본문 바로가기
Study

React Query

by Mishka 2022. 11. 28.
반응형

React Query는 React 어플리케이션에서 서버의 상태를 불러오고, 캐싱하며, 지속적으로 동기화 하고 업데이트 하는 작업을 도와 주는 라이브러리 입니다.

 

장점

* 캐싱
* Boilerplate 코드의 감소
* get 한 데이터에 update 가 발생하면 자동으로 get을 다시 수행한다.
* 동일 데이터 여러번 요청시 한번만 요청한다 (옵션에 따라 중복호출 허용 시간 조절 가능)
* 데이터가 오래되었다고 판단하면 다시 get (invalidateQueries)
* API 요청 수행을 위한 규격화된 방식 제공

사용

npx create-react-app my-app
cd my-app
yarn add react-query
yarn && yarn start

세팅

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      {/* devtools */}
      <ReactQueryDevtools initialIsOpen={true} />
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

useQuery

- 데이터를 get 하기 위한 api / post,update에는 useMutation을 사용

- 첫번째 파라미터로 unique Key가 들어가고, 두번째 파라미터로 비동기 함수(api 호출함수)가 들어갑니다.

- unique Key는 다른 컴포넌트에서도 해당 키를 사용하면 호출이 가능합니다.

  unique Key는 string 과 배열을 받는데 배열로 넘기면 0번 값은 string 값으로 다른 컴포넌트에서 부를 값이 들어가고 두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달됩니다.

- return 값은 api의 성공,실패 여부. 즉 api return값을 포함한 객체입니다.

- useQuery는 비동기로 작동합니다. 여러개의 비동기 query가 있다면 useQueries를 권장

- enabled를 사용하면 useQuery를 동기적으로 사용가능

 

예제

const Todos = () => {
  const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
    refetchOnWindowFocus: false, // react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행합니다. 그 재실행 여부 옵션 입니다.
    retry: 0, // 실패시 재호출 몇번 할지
    onSuccess: data => {
      // 성공시 호출
      console.log(data);
    },
    onError: e => {
      // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됩니다.)
      // 강제로 에러 발생시키려면 api단에서 throw Error 날립니다. (참조: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default)
      console.log(e.message);
    }
  });

  if (isLoading) {
    return <span>Loading...</span>;
  }

  if (isError) {
    return <span>Error: {error.message}</span>;
  }

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

* status 로 isLoding, isSucess를 한번에 처리 할 수도 있다.

function Todos() {
  const { status, data, error } = useQuery("todos", fetchTodoList);

  if (status === "loading") {
    return <span>Loading...</span>;
  }

  if (status === "error") {
    return <span>Error: {error.message}</span>;
  }

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

useQuery의 동기적 실행

enabled 옵션을 사용하면 useQuery를 동기적으로 사용 가능합니다.

useQuery의 3번째 인자로는 옵션값이 들어가는데 enabled값을 true일때 useQuery를 실행합니다. 이를 이용하면 동기적으로 함수를 실행 할 수 있습니다.

 

useQueries

useQuery를 비동기로 여러개 실행할 경우 어려움을 겪을수 있습니다. 이를 방지하기 위해 promise.all처럼 useQuery를 하나로 묶을수 있는 기능이 useQueries 입니다. promise.all처럼 하나의 배열에 각 쿼리에 대한 상태 값이 객체로 들어옵니다.

// 아래 예시는 롤 룬과, 스펠을 받아오는 예시입니다.
const result = useQueries([
  {
    queryKey: ["getRune", riot.version],
    queryFn: () => api.getRunInfo(riot.version)
  },
  {
    queryKey: ["getSpell", riot.version],
    queryFn: () => api.getSpellInfo(riot.version)
  }
]);

useEffect(() => {
  console.log(result); // [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}]
  const loadingFinishAll = result.some(result => result.isLoading);
  console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료
}, [result]);

useMutation

값을 바꿀때 사용하는 api입니다. return 값은 useQuery와 동일하다.

import { useState, useContext, useEffect } from "react";
import loginApi from "api";
import { useMutation } from "react-query";

const Index = () => {
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");

  const loginMutation = useMutation(loginApi, {
    onMutate: variable => {
      console.log("onMutate", variable);
      // variable : {loginId: 'xxx', password; 'xxx'}
    },
    onError: (error, variable, context) => {
      // error
    },
    onSuccess: (data, variables, context) => {
      console.log("success", data, variables, context);
    },
    onSettled: () => {
      console.log("end");
    }
  });

  const handleSubmit = () => {
    loginMutation.mutate({ loginId: id, password });
  };

  return (
    <div>
      {loginMutation.isSuccess ? "success" : "pending"}
      {loginMutation.isError ? "error" : "pending"}
      <input type="text" value={id} onChange={e => setId(e.target.value)} />
      <input
        type="password"
        value={password}
        onChange={e => setPassword(e.target.value)}
      />
      <button onClick={handleSubmit}>로그인</button>
    </div>
  );
};

export default Index;

update후에 get 실행

mutation 함수가 성공 할때, unique Key 로 맵핑된 get 함수로 invalidateQueries 에 넣어주면 됩니다.

const mutation = useMutation(postTodo, {
  onSuccess: () => {
    // postTodo가 성공하면 todos로 맵핑된 useQuery api 함수를 실행합니다.
    queryClient.invalidateQueries("todos");
  }
});

mutation에서 return된 값을 이용해서 get 함수의 파라미터를 변경해야 될 경우에는 setQueryData를 사용합니다.

const queryClient = useQueryClient();

const mutation = useMutation(editTodo, {
  onSuccess: data => {
    // data가 fetchTodoById로 들어간다
    queryClient.setQueryData(["todo", { id: 5 }], data);
  }
});

const { status, data, error } = useQuery(["todo", { id: 5 }], fetchTodoById);

mutation.mutate({
  id: 5,
  name: "nkh"
});

 

react Suspense 와 Error boundary 같이 사용하기

Suspense 사용하여 loading을 Error boundary를 사용하여 에러 핸들링을 더욱 직관적으로 할 수 있습니다.

suspense를 사용하기 위해 QueryClient에 옵션을 하나 추가합니다.

// src/index.js
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 0,
      suspense: true
    }
  }
});

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

함수에 suspense를 사용하는 예시

const { data } = useQurey("test", testApi, { suspense: true });

// 사용
return (
  // isLoading이 true이면 Suspense의 fallback 내부 컴포넌트가 보여집니다.
  // isError가 true이면 ErrorBoundary의 fallback 내부 컴포넌트가 보여집니다.
  <Suspense fallback={<div>loading</div>}>
    <ErrorBoundary fallback={<div>에러 발생</div>}>
      <div>{data}</div>
    </ErrorBoundary>
  </Supense>
);

 

Reference

https://kyounghwan01.github.io/blog/React/react-query/basic/#react-suspense%E1%84%8B%E1%85%AA-react-query-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5

https://tech.kakaopay.com/post/react-query-1/

반응형

댓글