๐Ÿค ์€์ง€log ๐Ÿค

[REACT] useQuery ์™€ useMutation ๋ณธ๋ฌธ

๐Ÿ’™ React

[REACT] useQuery ์™€ useMutation

Eun_zii 2023. 3. 30. 12:04
๐Ÿ’ก useQuery

API์—ฐ๋™์— ํŠนํ™”๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ Hook์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.
๋ฐ˜ํ™˜ ๊ฐ’์„ ํ™œ์šฉํ•˜์—ฌ ์„ฑ๊ณต, ์‹คํŒจ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ( isFetching, isLoading, error, state ๋“ฑ )

 

Status

  • isLoading : ํ˜„์žฌ ๋ฐ์ดํ„ฐ ์š”์ฒญ ์ค‘
  • isError : ์˜ค๋ฅ˜ ๋ฐœ์ƒ
  • isSuccess : ๋ฐ์ดํ„ฐ ์š”์ฒญ ์„ฑ๊ณต
  • data : ์š”์ฒญ ์„ฑ๊ณตํ•œ ๋ฐ์ดํ„ฐ
  • refetch : ๋‹ค์‹œ ์š”์ฒญ์„ ์‹œ์ž‘ํ•˜๋Š” ํ•จ์ˆ˜
const Ex = () => {
  const result = useQuery('posts', postData);
  const { data, error, isLoading } = result;

  if (isLoading) return <>Loading...</>;
  if (error) return alert(Error!);

  return <Container>{data}</Container>
}

 

Option

  • staleTime
    • fresh -> stale ๊นŒ์ง€์˜ ์‹œ๊ฐ„
    • fresh : ๋ฐ์ดํ„ฐ fetch๊ฐ€ ๋ง‰ ์ด๋ฃจ์–ด์ง„ ์ƒํƒœ
    • stale : not fresh. ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ์ด๋ฏ€๋กœ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•จ.
    • fresh ์ƒํƒœ์—์„œ unmount ์ดํ›„ ๋‹ค์‹œ mount ๋˜์–ด๋„ fetch ํ•˜์ง€ ์•Š๊ณ  fresh ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
    • ๊ธฐ๋ณธ ๊ฐ’์€ 0, 0 ~ Infinity๊นŒ์ง€ ์„ค์ • ๊ฐ€๋Šฅ
  • cacheTime
    • stale ์ƒํƒœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ unmount ๋œ ์ดํ›„ ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์•„์žˆ๋Š” ์‹œ๊ฐ„
    • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์บ์‹œ ๋ฐ์ดํ„ฐ๋Š” GC๊ฐ€ ์ˆ˜์ง‘ํ•จ
    • ๊ทธ ์ „์— ๋‹ค์‹œ mount ๋œ๋‹ค๋ฉด fetch ๋˜๋Š” ๋™์•ˆ์—๋Š” ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
    • ๊ธฐ๋ณธ ๊ฐ’์€ 5๋ถ„, 0 ~ Infinity๊นŒ์ง€ ์„ค์ • ๊ฐ€๋Šฅ
  • refetchOnMount ( boolean | "always" )
    • stale ์ผ ๋•Œ ๋งˆ์šดํŠธ ์‹œ refetch ์‹คํ–‰ ์—ฌ๋ถ€
    • ๊ธฐ๋ณธ ๊ฐ’์€ true.
    • always ์ผ ๊ฒฝ์šฐ fresh ์ƒํƒœ์—์„œ๋„ refetch ํ•œ๋‹ค.
  • retry (boolean | number )
    • ์ฟผ๋ฆฌ๊ฐ€ ์‹คํŒจํ–ˆ์„ ๋•Œ ์žฌ์‹œ๋„ํ•˜๋Š” ์˜ต์…˜
    • true : ๊ณ„์† ์žฌ์‹œ๋„
    • false : ์žฌ์‹œ๋„ X
    • number : ๊ทธ ์ˆ˜ ๋งŒํผ ์žฌ์‹œ๋„
    • ๊ธฐ๋ณธ ๊ฐ’์€ 3
  • enabled
    • true์ธ ๊ฒฝ์šฐ๋งŒ ์ฟผ๋ฆฌ ์‹คํ–‰
const Ex = () => {
  const result = useQuery('posts', postData, { 
    //Options
    enabled: true,
    refetchOnMount: true,
  });
);

 

๐Ÿ’ก useMutation

๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑ / ์—…๋ฐ์ดํŠธ / ์‚ญ์ œํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
optimistic update์„ ํ™œ์šฉํ•ด์„œ ์„ฑ๊ณต์„ ์˜ˆ์ƒํ•˜๋ฉฐ ๋ฏธ๋ฆฌ UI๋ถ€ํ„ฐ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Option

  • onMutate: (variables:TVariables) => Promise<TContext|void>|TContext|void
    • mutationFn์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— ๋จผ์ € ์‹คํ–‰ํ•  ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
    • mutation ํ•จ์ˆ˜๊ฐ€ ์ „๋‹ฌ๋ฐ›์€ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋™์ผํ•˜๊ฒŒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
    • optimistic update ์‚ฌ์šฉ ์‹œ ์œ ์šฉํ•œ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
    • ์—ฌ๊ธฐ์„œ ๋ฐ˜ํ™˜๋œ ๊ฐ’์€ onError, onSettled ํ•จ์ˆ˜์— ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. 

  • onSuccess: (data: TData, variables: TVariables, context?: TContext) => Promise<unknown> | void
    • mutation()์ด ์„ฑ๊ณตํ•˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‹คํ–‰ ๋ฉ๋‹ˆ๋‹ค.
      • invalidationQueries(QueryKey)๋กœ ํ•ด๋‹น ์ฟผ๋ฆฌ๋ฅผ invalidate ํ•˜๊ณ  refetch
      • setQueryData๋กœ ์ง์ ‘ ์บ์‹ฑ๋œ ์ฟผ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •. (Optimistic Update) refetch๊ฐ€ ์ผ์–ด๋‚˜์ง€ ์•Š์Œ.

  • onError: (err: TError, variables: TVariables, context?: TContext) => Promise<unknown> | void
    • mutation์ด ์—๋Ÿฌ๋ฅผ ๋งŒ๋‚ฌ์„ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

  • onSettled: (data: TData, error: TError, variables: TVariables, context?: TContext) => Promise<unknown> | void
    • mutation()์˜ ์„ฑ๊ณต / ์—๋Ÿฌ ์—ฌ๋ถ€์— ์ƒ๊ด€์—†์ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›์Šต๋‹ˆ๋‹ค. 

  • mutationFn (variables: TVariables) => Promise<TData>
    • Required
    • ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. (์‰ฝ๊ฒŒ ๋งํ•ด api ์š”์ฒญํ•˜๋Š” ํ•จ์ˆ˜)
    • variables ๋Š” mutate๊ฐ€ ์ „๋‹ฌํ•˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

Return

  • mutate: (variables: TVariables, { onSuccess, onSettled, onError }) => void
    • mutation์„ ์‹คํ–‰์‹œํ‚ค๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
    • variables ๋Š” mutationFn์— ์ „๋‹ฌํ•˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
import React from "react";
import axios from "axios";
import { useMutation } from "react-query";

import "./App.css";

function App() {
  const fetchAdd = async (info) => {
    const { data } = await axios.post(`/add`, info);

    return data;
  };

  const { mutate, isLoading, isSuccess, isError } = useMutation(fetchAdd, {
    onMutate: (variables) => {
      // variables : {id: 1}
      console.log("onMutate", variables);
    },
    onError: (error, variables, context) => {
      // error
    },
    onSuccess: (data, variables, context) => {
      console.log("success", data, variables, context);
    },
    onSettled: (data, error, variables, context) => {
      // end
    },
  });

  const handleSubmit = () => {
    // onMutate ์‹คํ–‰ => ์„ฑ๊ณต์‹œ onSuccess ์‹คํ–‰ => ๋๋‚˜๋ฉด onSettled ์‹คํ–‰
    mutate({ id: 1 });
  };
}
export default App;

 

update ์ดํ›„ Get ์š”์ฒญ์„ ํ†ตํ•œ ์ตœ์‹ ํ™”

  • react-query ์žฅ์ ์œผ๋กœ Updateํ›„์— Get ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ์ง„ํ–‰ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • mutation ํ•จ์ˆ˜๊ฐ€ ์„ฑ๊ณตํ•  ๋•Œ, unique key๋ฅผ invalidateQueries์— ๋„ฃ์–ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
    • invalidateQueries๊ฐ€ ์‹คํ–‰๋˜๋ฉด ํ•ด๋‹น Query๋Š” ๋ฌดํšจํ™”๋˜๋ฉฐ refetching์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
    • ์ฆ‰ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜์ง€ ์•Š์•„๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐฑ์‹  ๋ฉ๋‹ˆ๋‹ค.
const queryClient = useQueryClient();

const mutation = useMutation(fetch, {
  onSuccess: () => {
    // postTodo๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด todos๋กœ ๋งตํ•‘๋œ useQuery api ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    queryClient.invalidateQueries("key");
  }
});
  • ํ•ด๋‹น Query์— ๋Œ€ํ•ด refetching์„ ์›ํ•˜์ง€ ์•Š๊ณ  ๋ฌดํšจํ™”๋งŒ ์›ํ•œ๋‹ค๋ฉด refetchActive ์˜ต์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
queryClient.invalidateQueries("key", {
 refetchActive: false, 
});

 

Optimistic Update

  • useMutation์ด ์„ฑ๊ณตํ•  ๊ฒƒ์ด๋ผ ๊ฐ€์ •ํ•˜๊ณ  ๋ฏธ๋ฆฌ ํ™”๋ฉด์˜ UI๋ฅผ ๋ฐ”๊พธ๊ณ  ๋‚˜์„œ, ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ํ™•์ • / ๋กค๋ฐฑํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
  • setQueryData ์˜ต์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
import React from "react";
import axios from "axios";
import { useMutation } from "react-query";

import "./App.css";

function App() {
  const fetchAdd = async (info) => {
    const { data } = await axios.post(`/add`, info);

    return data;
  };

  const queryClient = useQueryClient();

  const { mutate, isLoading, isSuccess, isError } = useMutation(fetchAdd, {
    onMutate: async (variables) => {
      // ์š”์ฒญํ•œ Query ์ทจ์†Œ ํ•จ์ˆ˜
      // ์ฆ‰ ํ•ด๋‹น Query์— ๋Œ€ํ•œ refetching์„ ๋ง‰๋Š” ๊ฒƒ => onMutate์—์„œ ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ๋“ค์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ธฐ ์œ„ํ•ด
      await queryClient.cancelQueries("key");

      // ๊ธฐ์กด Query๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜ ( ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด undefinde ๋ฐ˜ํ™˜ )
      const previousValue = queryClient.getQueryData("key");

      if (previousValue) {
        // setQueryData(): Query์˜ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋™๊ธฐ ํ•จ์ˆ˜ ( Query๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ์ƒ์„ฑ )
        // ์ „๋‹ฌ๋ฐ›์€ variables๊ฐ’์„ ์ฆ‰์‹œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋กœ ์—…๋ฐ์ดํŠธ
        queryClient.setQueryData("key", (oldData) => [...oldData, variables]);
      }

      // ์ด์ „ ๊ฐ’ ๋ฆฌํ„ด
      return { previousValue };
    },
    onError: (error, variables, context) => {
      if (context?.previousValue) {
        // error๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด onMutate์—์„œ ๋ฐ˜ํ™˜๋œ ๊ฐ’์œผ๋กœ ๋‹ค์‹œ ๋กค๋ฐฑ
        queryClient.setQueryData("key", context.previousValue);
      }
    },
    onSuccess: (data, variables, context) => {},
    onSettled: (data, error, variables, context) => {
      // mutation์ด ์™„๋ฃŒ๋˜๋ฉด ์„ฑ๊ณต ์œ ๋ฌด์™€ ๊ด€๊ณ„์—†์ด ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™” ์‹œํ‚ค๊ณ  ์ƒˆ๋กœ ๊ฐฑ์‹ 
      queryClient.invalidateQueries("key");
    },
  });

  const handleSubmit = () => {
    // onMutate ์‹คํ–‰ => ์„ฑ๊ณต์‹œ onSuccess ์‹คํ–‰ => ๋๋‚˜๋ฉด onSettled ์‹คํ–‰
    mutate({ id: 1 });
  };
}
export default App;
728x90