관리 메뉴

거니의 velog

(51) 리액트 쿼리와 리코일 3 본문

SpringBoot_React 풀스택 프로젝트

(51) 리액트 쿼리와 리코일 3

Unlimited00 2024. 3. 11. 18:45

3. 상품등록 처리

* 리액트 쿼리에서 가장 중요한 기능은 useQuery()와 useMutation() 이다. SQL로 비유하자면 useQuery()가 select를 위해서 사용된다면, useMutation()은 insert/update/delete를 위해서 사용된다. useMutation() 은 파라미터로 서버를 호출하는 함수를 전달하고 mutate()를 이용해서 처리 결과에 대한 다양한 정보를 얻을 수 있다.

* 상품등록 기능을 처리하는 components/products/AddComponent.js를 수정한다.

* AddComponent에 useMutation()이 적용되므로 모달창을 처리하기 위한 fetching이나 result와 관련된 내용들을 모두 삭제하고 useMutation()을 적용한다.

import React, { useRef, useState } from "react";
import { postAdd } from "../../api/productsApi";
import FetchingModal from "../common/FetchingModal";
import ResultModal from "../common/ResultModal";
import useCustomMove from "../../hooks/useCustomMove";
import { useMutation } from "@tanstack/react-query";

const initState = {
  pname: "",
  pdesc: "",
  price: 0,
  files: [],
};

const AddComponent = () => {
  // 기본적으로 필요
  const [product, setProduct] = useState({ ...initState });
  const uploadRef = useRef();
  const { moveToList } = useCustomMove();

  // 입력값 처리
  const handleChangeProduct = (e) => {
    product[e.target.name] = e.target.value;
    setProduct({ ...product });
  };

  const addMutation = useMutation((product) => postAdd(product)); // 리액트 쿼리

  const handleClickAdd = (e) => {
    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);

    addMutation.mutate(formData); // 기존 코드에서 변경
  };

  const closeModal = () => {
    moveToList({ page: 1 }); // 모달 창이 닫히면 페이지로 이동
  };

  return (
    <div className="border-2 border-sky-200 mt-10 m-2 p-4">
    
      {/* 생략 모달 창들은 잠시 삭제
        {fetching ? <FetchingModal /> : <></>}
        {result ? (
          <ResultModal
            title={"Product Add Result"}
            content={`${result}번 등록 완료`}
            callbackFn={closeModal}
          />
        ) : (
          <></>
        )}
      */}
      
      (...)
      
    </div>
  );
};

export default AddComponent;

* 변경된 코드를 실행하면 새로운 상품이 등록되기는 하지만 화면상에 변화는 없으므로 직접 목록 페이지로 이동해서 결과를 확인한다.


(1) useMutation()의 반환값

* useMutation()의 반환값들은 'isXXX'와 'data, error'와 같은 실제 결과 데이터들이다. 기존의 예제와 같이 FetchingModal과 ResultModal을 보이도록 코드를 수정해 보자.

  return (
    <div className="border-2 border-sky-200 mt-10 m-2 p-4">
      {addMutation.isLoading ? <FetchingModal /> : <></>}
      {addMutation.isSuccess ? (
        <ResultModal
          title={"Product Add Result"}
          content={`Product Add Success ${addMutation.data.result}`}
          callbackFn={closeModal}
        />
      ) : (
        <></>
      )}
      
  (...)

* 브라우저에서 새로운 상품을 등록하면 처리 중인 경우와 처리 후 모달창이 보이게 된다.


[등록 후 처리 invalidateQueries()]

* 목록 화면에서 사용하는 useQuery()의 staleTime이 짧은 경우에는 문제가 되지 않겠지만, staleTime이 긴 경우에는 새로운 상품을 등록해도 목록으로 이동한 경우에 서버를 호출하지 않기 때문에 기존 페이지가 그대로 유지되는 현상이 생길 수 있다.

* 예를 들어 ListComponent에서 staleTime을 1분으로 지정하고 새로운 상품을 추가하면?

const ListComponent = () => {
  const { page, size, moveToList, moveToRead, refresh } = useCustomMove();
  const { moveToLoginReturn } = useCustomLogin();
  const { isFetching, data, error, isError } = useQuery(
    ["products/list", { page, size, refresh }], // refresh 추가
    () => getList({ page, size }),
    { staleTime: 1000 * 5 * 12 } // staleTime 추가
  );

* 125번으로 새로운 상품이 등록되었고, 모달창을 닫아서 목록 화면으로 이동하게 되는데 이 경우 125번 상품은 아직 보이지 않고 124번까지만 보이는 것을 확인할 수 있다.

* 이를 해결하기 위해서는 AddComponent에서 모달창을 닫을 때 invalidateQueries()를 호출해야 한다.

import React, { useRef, useState } from "react";
import { postAdd } from "../../api/productsApi";
import FetchingModal from "../common/FetchingModal";
import ResultModal from "../common/ResultModal";
import useCustomMove from "../../hooks/useCustomMove";
import { useMutation, useQueryClient } from "@tanstack/react-query";

(...)

const AddComponent = () => {
  (...)

  const queryClient = useQueryClient();

  const closeModal = () => {
    queryClient.invalidateQueries("products/list");
    moveToList({ page: 1 }); // 모달 창이 닫히면 페이지로 이동
  };

  return (
    <div className="border-2 border-sky-200 mt-10 m-2 p-4">
      (...)
    </div>
  );
};

export default AddComponent;

* 변경된 코드를 이용하면 새로운 상품이 등록된 후에 기존 상품 목록 데이터들이 invalidate되기 때문에 다시 서버를 호출해서 방금 추가된 상품이 목록에 출력되는 것을 확인할 수 있다.