일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 자동차수리시스템
- 참조형변수
- 사용자예외클래스생성
- GRANT VIEW
- 객체 비교
- 예외처리
- 오라클
- 컬렉션 타입
- 인터페이스
- EnhancedFor
- 한국건설관리시스템
- 어윈 사용법
- 대덕인재개발원
- 컬렉션프레임워크
- 환경설정
- 정수형타입
- abstract
- 자바
- 추상메서드
- 예외미루기
- 다형성
- Java
- oracle
- exception
- NestedFor
- cursor문
- 메소드오버로딩
- 집합_SET
- 생성자오버로드
- 제네릭
- Today
- Total
거니의 velog
(52) 리액트 쿼리와 리코일 4 본문
4. 상품 수정 처리
* 상품 수정은 조회와 등록 기능이 같이 존재하기 때문에 useQuery()와 useMutation()을 같이 사용해야 한다. 상품의 수정 처리는 기본적으로 다음의 흐름을 따라서 처리한다.
- useQuery()를 이용해서 상품 데이터를 가져온 후 컴포넌트의 상태 값으로 지정한다.
- <input>을 이용해서 컴포넌트의 상태로 유지되는 데이터를 수정한다.
- 수정이나 삭제를 처리한 후 화면을 이동하게 된다.
* 리액트 쿼리 3 버전까지는 useQuery() 의 파라미터로 지정되는 옵션에는 onSuccess를 지정해서 서버의 데이터를 직접 useState()가 사용하는 상태로 변경할 수 있었지만 5 버전 부터는 deprecated 되므로 다른 방법을 사용해야 한다.
(1) 조회 및 상태 처리
* components/products/ModifyComponent 에서는 우선 useQuery()를 이용해서 상품 데이터를 가져오는 코드를 작성해야 한다.
* 상품 데이터를 가져오는 코드를 추가하면 아래와 같이 된다.
import React, { useEffect, useState, useRef } from "react";
import { getOne, putOne, deleteOne } from "../../api/productsApi";
import FetchingModal from "../common/FetchingModal";
import { API_SERVER_HOST } from "../../api/todoApi";
import useCustomMove from "../../hooks/useCustomMove";
import ResultModal from "../common/ResultModal";
import { useQuery } from "@tanstack/react-query";
const initState = {
(...)
};
const host = API_SERVER_HOST;
const ModifyComponent = ({ pno }) => {
(...)
const query = useQuery(["products", pno], () => getOne(pno));
return (
<div className="border-2 border-sky-200 mt-10 m-2 p-4">
(...)
</div>
);
};
export default ModifyComponent;
* useQuery()가 성공했을 경우 setProduct()를 하기 위해서 과거에는 onSuccess를 옵션으로 지정할 수 있었지만, onSuccess를 지정하는 방식은 deprecated 되었으므로 상태를 이용해서 처리해야 한다.
* 때문에 useQuery() 결과를 직접 setProduct()로 지정해야 하는데 이 경우 무한히 컴포넌트의 상태가 변경되었기 때문에 무한히 반복적인 코드가 된다.
const query = useQuery(["products", pno], () => getOne(pno));
// 절대로 실행하면 안되는 무한 반복
if (query.isSuccess) {
setProduct(query.data);
}
* 이 문제를 해결하기 위해서는 useEffect()를 이용해서 온전히 데이터가 존재하고 성공했을 경우에만 setProduct()를 호출하도록 조정한다. 또한, 조회와 달리 수정 중간에 다시 API 서버를 호출하지 않도록 staleTime을 무한(infinity)으로 설정한다.
const query = useQuery(["products", pno], () => getOne(pno), {
staleTime: Infinity,
});
useEffect(() => {
if (query.isSuccess) {
setProduct(query.data);
}
}, [pno, query.data, query.isSuccess]);
* ModifyComponent에서 모달창이나 이벤트 처리를 제외한 코드는 아래와 같다.
import React, { useEffect, useState, useRef } from "react";
import { getOne, putOne, deleteOne } from "../../api/productsApi";
import FetchingModal from "../common/FetchingModal";
import { API_SERVER_HOST } from "../../api/todoApi";
import useCustomMove from "../../hooks/useCustomMove";
import { useQuery } from "@tanstack/react-query";
const initState = {
pno: 0,
pname: "",
pdesc: "",
price: 0,
delFlag: false,
uploadFileNames: [],
};
const host = API_SERVER_HOST;
const ModifyComponent = ({ pno }) => {
const { moveToList, moveToRead } = useCustomMove();
const [product, setProduct] = useState(initState);
const uploadRef = useRef();
const query = useQuery(["products", pno], () => getOne(pno), {
staleTime: Infinity,
});
useEffect(() => {
if (query.isSuccess) {
setProduct(query.data);
}
}, [pno, query.data, query.isSuccess]);
const handleChangeProduct = (e) => {
product[e.target.name] = e.target.value;
setProduct({ ...product });
};
const deleteOldImages = (imageName) => {
const resultFileNames = product.uploadFileNames.filter(
(fileName) => fileName !== imageName
);
product.uploadFileNames = resultFileNames;
setProduct({ ...product });
};
const handleClickModify = () => {
const files = uploadRef.current.files;
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append("files", files[i]);
}
//other data
formData.append("pname", product.pname);
formData.append("pdesc", product.pdesc);
formData.append("price", product.price);
formData.append("delFlag", product.delFlag);
for (let i = 0; i < product.uploadFileNames.length; i++) {
formData.append("uploadFileNames", product.uploadFileNames[i]);
}
// 기존 처리 관련 코드 삭제
};
const handleClickDelete = () => {
// 기존 처리 코드 삭제
};
const closeModal = () => {
// 기존 코드 삭제
};
return (
<div className="border-2 border-sky-200 mt-10 m-2 p-4">
{/* 기존에 모달 창 부분 삭제 */}
(...)
</div>
);
};
export default ModifyComponent;
* 브라우저는 ModifyComponent의 동작 과정에서 API 서버를 여러 번 호출하지 않는지 확인한다.
* 조회 과정에서 FetchingModal을 보여주는 코드를 추가한다(staleTime을 Infinity로 지정한 이유는 상품 수정 과정에서 시간이 오래 걸리는 것을 대비하기 위함이다).
return (
<div className="border-2 border-sky-200 mt-10 m-2 p-4">
{query.isFetching ? <FetchingModal /> : <></>}
(...)
</div>
);
(2) 삭제 처리
* 삭제 처리에는 useMutation을 활용해야 한다. 또한 삭제 후 결과를 보여주는 ResultModal을 활용해야 한다.
* 코드에서는 delMutation을 선언하고 delMutation의 isSuccess를 이용해서 삭제 후에 리액트 쿼리의 데이터를 삭제하고 이동하도록 처리한다.
import React, { useEffect, useState, useRef } from "react";
import { getOne, putOne, deleteOne } from "../../api/productsApi";
import FetchingModal from "../common/FetchingModal";
import { API_SERVER_HOST } from "../../api/todoApi";
import useCustomMove from "../../hooks/useCustomMove";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import ResultModal from "../common/ResultModal";
(...)
const delMutation = useMutation((pno) => deleteOne(pno));
const queryClient = useQueryClient();
const handleClickDelete = () => {
delMutation.mutate(pno);
};
const closeModal = () => {
if (delMutation.isSuccess) {
queryClient.invalidateQueries(["products", pno]);
queryClient.invalidateQueries(["products/list"]);
moveToList();
}
};
* 화면에서는 delMutation의 isLoading과 isSuccess를 이용해서 모달창을 처리한다(ResultModal에 대한 import 필요).
return (
<div className="border-2 border-sky-200 mt-10 m-2 p-4">
{query.isFetching || delMutation.isLoading ? <FetchingModal /> : <></>}
{delMutation.isSuccess ? (
<ResultModal
title={"처리 결과"}
content={"정상적으로 처리되었습니다"}
callbackFn={closeModal}
></ResultModal>
) : (
<></>
)}
(...)
</div>
);
* 브라우저에서는 삭제 처리 동안 FetchingModal이 보이고 삭제된 후에는 ResultModal을 이용할 수 있게 된다. ResultModal이 종료되면 다시 목록 화면으로 이동하게 된다.
(3) 수정 처리
* 수정 처리 역시 useMutation()을 이용해서 수정 처리한 후에 이동하도록 작성한다.
const modMutation = useMutation((product) => putOne(pno, product));
const handleClickModify = () => {
const files = uploadRef.current.files;
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append("files", files[i]);
}
//other data
formData.append("pname", product.pname);
formData.append("pdesc", product.pdesc);
formData.append("price", product.price);
formData.append("delFlag", product.delFlag);
for (let i = 0; i < product.uploadFileNames.length; i++) {
formData.append("uploadFileNames", product.uploadFileNames[i]);
}
modMutation.mutate(formData);
};
* 수정된 후의 모달창이 닫히면 조회 화면으로 이동하게 한다. 이때 리액트 쿼리에 보관된 데이터는 삭제하기 위해서 invalidateQueries()를 이용한다.
const closeModal = () => {
if (delMutation.isSuccess) {
queryClient.invalidateQueries(["products", pno]);
queryClient.invalidateQueries(["products/list"]);
moveToList();
return;
}
if (modMutation.isSuccess) {
queryClient.invalidateQueries(["products", pno]);
queryClient.invalidateQueries(["products/list"]);
moveToRead(pno);
}
};
* 모달창들이 보이는 조건에 modMutation 관련 조건들을 추가한다.
return (
<div className="border-2 border-sky-200 mt-10 m-2 p-4">
{query.isFetching || delMutation.isLoading || modMutation.isLoading ? (
<FetchingModal />
) : (
<></>
)}
{delMutation.isSuccess || modMutation.isSuccess ? (
<ResultModal
title={"처리 결과"}
content={"정상적으로 처리되었습니다"}
callbackFn={closeModal}
></ResultModal>
) : (
<></>
)}
* 브라우저를 통해서 상품정보가 수정되면 자동으로 조회 화면으로 이동하는지 확인한다.
'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글
(54) 리액트 쿼리와 리코일 6 (0) | 2024.03.11 |
---|---|
(53) 리액트 쿼리와 리코일 5 (0) | 2024.03.11 |
(51) 리액트 쿼리와 리코일 3 (0) | 2024.03.11 |
(50) 리액트 쿼리와 리코일 2 (0) | 2024.03.11 |
(49) 리액트 쿼리와 리코일 1 (0) | 2024.03.11 |