리액트 쿼리 사용하기

아 헷갈려...

 

일단 리액트 쿼리란?

 

데이터를 서버에서 가져와서 화면에 보여주는 것을 더 쉽게 해주는 도구다.

특히 서버에서 데이터를 가져오고, 캐싱하고, 다시 가져오는 것들을 자동으로 해준다.

 

데이터를 쉽게 가져오고 관리할 수 있도록 도와주므로 웹사이트에서 사용자 정보, 게시글 목록을 가져올 때 사용할 수 있다.

 

먼저 React Query를 설치하고, 

yarn add @tanstack/react-query

 

App.jsx 나 Main.jsx 등에 provider를 이용해서 적용한다.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

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

 

요기까지가 기본 설정


 

1. useQuery

리액트 쿼리에서 데이터를 가져오는 데 사용하는 훅이다.

웹사이트에서 데이터를 가져와서 보여주려면 여러 단계가 필요한데 (서버에 요청 보내기, 응답 받아오기, 에러 처리하기)

useQuery를 사용하면 간단하게 할 수 있다.

더보기
더보기

훅은 리액트 컴포넌트에서 어떤 기능을 추가하기 위해 사용하는 함수다.

ex. useState는 상태를 관리하는 훅, useQuery는 데이터를 서버에서 가져오는 훅

먼저 useQuery를 임포트 해준다.

import { useQuery } from '@tanstack/react-query';

 

 

그리고 서버에서 데이터를 가져온다.

const fetchUser = async () => {
  const response = await axios.get('https://api.example.com/user');
  return response.data; 
};

https://api.example.com/user요기에서 사용자 정보를 가져오는 함수인데, 서버에서 사용자 정보를 가져와서 가져온 데이터를 반환한다.

 

그럼 이제 useQuery를 사용해서 데이터를 가져온다.

const {
  data: data: user, isPostLoading, isPostError,} = useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
});

 

queryKey는 쿼리를 식별하는 유니크한 키다. 이 키를 사용해서 어떤 데이터를 가져와야 하는지 구분한다.

(쿼리 키를 사용하면 리액트 쿼리가 어떤 데이터를 가져오고 있는지, 그 데이터가 캐시된 상태인지 쉽게 알 수 있다.

예를 들어서 같은 쿼리 키를 가진 데이터가 여러 컴포넌트에서 사용된다면, 리액트 쿼리는 그 데이터를 한번만 가져오고

여러 컴포넌트에서 공유할 수 있다.)

 

여기서는 user가 쿼리 키다. 가져온 데이터를 user라는 이름으로 사용하고 있는 것이다. 이 키를 통해서 리액트 쿼리는 fetchUser 함수가 반환하는 데이터를 관리한다.

더보기
더보기

useQuery 훅은 세 가지 상태를 제공하는데

 

data는 가져온 데이터가 저장되고, isLoading은 데이터를 가져오는 중인지 알려준다. isError는 데이터를 가져오는 중에 문제가 생겼는지 알려준다.

queryFn는 서버에서 데이터를 가져오는 함수다. useQuery훅이 실행되면 먼저 queryFn으로 지정된 fetchUser 함수를 호출하고, 이 함수는 https://api.example.com/user 요기서 데이터를 가져오는 것이다.

 

그래서 fetchUser 함수가 데이터를 가져오면 userQuery 훅은 이 데이터를 data 변수에 저장한다.

그럼 data 변수는 컴포넌트에서 사용할 수 있게 되는 것이다.

더보기
더보기
  queryKey: ['todos'],
  queryKey: ['posts', detailId],

 

이렇게 하나만 쓰는 경우도, 두 개를 쓰는 경우도 있는데

차이는 todos 리스트 전체를 가져오는 것과, posts 리스트에서 특정 id를 가진 post 하나를 가져올 때의 차이다.

 

 

2. useMutation

useMutation은 데이터 변경 작업을 처리할 때 사용하는 훅이다. (추가, 수정, 삭제)

또 서버에서 데이터를 추가하거나 업데이트 하거나 삭제할 수 있고, 자동으로 ui를 업데이트 할 수 있다.

useQuery는 데이터를 가져오는 데 사용!

 

일단 새로운 사용자를 추가하는 상황이라고 가정하고, 서버에 데이터를 추가하는 함수를 만들어보자.

import axios from 'axios';

// 새로운 사용자를 추가하는 함수
const addUser = async (newUser) => {
  const response = await axios.post('https://api.example.com/user', newUser);
  return response.data;
};

newUser은 함수 addUser에 전달되는 인자인데, 새로운 사용자의 정보를 담고 있다.

axios.post는 두 가지 인자를 받는데 url과 서버에 보낼 데이터를 받기 때문이다.

 

이제 useMutation 훅을 사용해본다.

먼저 리액트 쿼리 훅을 하나 써줘야 하는데,

const queryClient = useQueryClient();

이것이당. 기존 쿼리 데이터를 업데이트 하거나 무효화 하는데 사용하는데,

이 훅을 사용하면 다른 컴포넌트에서 사용되고 있는 데이터를 가져올 수 있다.

const addMutation = useMutation(addUser, {
  onSuccess: () => {
    // 사용자가 추가된 후, 기존 'users' 데이터를 다시 가져온다
    queryClient.invalidateQueries('users');
  },
});

이제 useMutation을 사용해서 addUser 함수를 호출할 준비를 한다.

새로운 사용자를 서버에 추가하려면 addUser 함수가 필요하기 때문이다.

이제 사용자가 추가되었으면 화면에 최신 사용자 목록을 보여주고 싶다.

그래서 onSuccess 콜백 함수 (사용자를 성공적으로 추가했을 때 실행됨)에서

queryClient.invalidateQueries('users') 를 사용하는데 이러면 users 쿼리 키를 가진 데이터를 다시 가져온다.

이렇게 하면 화면에 최신 사용자 목록이 반영된다.

 


헷갈리니까 여기서 한번 정리를 하자면,

 

1. 새로운 사용자 정보를 추가하는 버튼 클릭

const handleAddUser = () => {
  const newUser = { name: 'John Doe', email: 'john.doe@example.com' };
  addMutate.mutate(newUser);
};

 

 

이 버튼을 클릭하면 새로운 사용자가 추가된다.

이 버튼은 newUser 라는 객체를 만드는데 여기엔 name과 email 정보가 들어있다.

여기서 addMutate를 사용해서 newUser 정보를 서버에 보내는데,

이러면 새로운 사용자를 추가하는 과정이 시작된다.

 

2. 서버에 요청 보내기

addMutate.mutate(newUser); 이게 호출되면 useMutation 훅에서 정리한 addUser 함수가 실행된다.

const addMutation = useMutation(addUser, {

이 addUser 함수는 newUser 정보를 서버에 보낸다.

const addUser = async (newUser) => {
  const response = await axios.post('https://api.example.com/user', newUser);
  return response.data;
};

그럼 여기서는 axios.post를 사용해 newUser 정보를 서버에 보낸다.

그리고 응답을 받으면 데이터를 반환한다.

 

3. 추가 성공

const mutation = useMutation(addUser, {
  onSuccess: () => {
    // 사용자가 추가된 후, 기존 'users' 데이터를 다시 가져온다
    queryClient.invalidateQueries('users');
  },
});

 

 

추가가 성공하면 onSuccess 콜백함수가 실행된다.

 

4. onSuccess 콜백 함수 실행

queryClient.invalidateQueries('users')

그럼 이 부분이 호출이 되고, 이 함수는 users 라는 키를 가진 데이터를 다시 가져오게 하므로

사용자 목록이 최신 상태로 업데이트 된다.

그리고 최신 사용자 목록을 다시 가져와서 화면에 보여준다.


이건 그냥 저번에 쓴 거 아까워서 냅뒀다.

더보기
더보기

삭제 함수는 이렇게 쓴다.

const deletePost = async (id) => {
  await axios.delete(`http://localhost:5000/expenses/${id}`);
};

그냥 삭제만 해주면 되니까 return을 쓰지 않는다.

 

const delPostMutation = useMutation({
  mutationFn: deletePost,
  onSuccess: () => {
    queryClient.invalidateQueries(['posts']);
    navigate('/');
  },
});

그리고 useMutation을 이렇게 써줬는데,

mutationFn은 실제로 변화를 일으키는 함수고, deletePost 함수를 지정해서 삭제 작업을 수행한다.

onSuccess는 변화를 일으키는 작업이 완료된 후에 실행되는 콜백 함수다.

 

그리고 삭제 버튼에도 추가해줘야 하는 게 있는데

 delPostMutation.mutate(detailId);

요것이다. mutate 함수를 호출하는 부분인데, 이것은 useMutation 훅에서 제공하는 함수다.

mutate 함수는 호출될 때 인자를 받을 수 있는데 여기서는 detailId를 인자로 받는다.

그리고 이 인자는 mutationFn(deletePost)에 전달된다.

그래서 deletePost 함수가 실행될 때 id 인자로 detailId로 전달된다.  즉, deletePost(detailId)와 같다.

이 detailId는 삭제할 데이터의 id로 사용되는 것이고.

<const id = queryKey[1]; 이거랑 다름! 이름은 같은데 다른 역할을 한다.>


그리고 뭔지 몰라서 정리하는 부분

 

이 코드는 새로고침을 하면 프로필 사진과 닉네임을 바로 갖고 오지 못해서 넣은 코드인데,

헤더는 항상 최신 사용자 정보를 가져와야 하고, 새로고침하거나 다시 열때마다 사용자 정보를 헤더에 즉시 반영해야 하므로 useQuery를 사용해서 사용자 정보를 서버에서 가져온다.

const { data, isLoading, isError, isSuccess } = useQuery({
  queryKey: ['userInfo'], // 쿼리 키 설정
  queryFn: () => fetchUserInfo(isAuthenticated, dispatch), // 쿼리 함수 설정
  enabled: !!isAuthenticated, // 쿼리 실행 조건 설정
  onSuccess: (data) => { // 쿼리 성공시 실행되는 콜백
    dispatch(setUserInfo(data)); // 가져온 데이터를 상태에 저장
  },
});

이 부분이다.

 

일단 데이터를 가져오는데 쿼리 키는 userInfo로 지정.

그리고 fetchUserInfo 함수를 사용해서 데이터를 가져온다. 저 fetchUserInfo는 유저 정보를 담고 있다.

인자를 전달해야 해서 저렇게 쓰는데, fetchUserInfo만 쓰면 인자를 전달하지 못하고, 화살표 함수를 쓰지 않으면 함수를 즉시 실행해버린다.

 

enabled는 이 쿼리를 실행할지 여부를 결정한다. !!isAuthenticated는 isAuthenticated 값이 true일 때만 쿼리를 실행하게 한다. (사용자가 로그인한 상태일 때만)

 

서버에서 데이터를 성공적으로 가져오면 onSuccess 콜백이 실행된다. 이 콜백은 dispatch(setUserInfo(data)) 호출해서 가져온 데이터를 상태에 저장한다.

 

상태가 업데이트 되면 컴포넌트가 다시 렌더링 되어 최신 사용자 정보를 화면에 표시한다.

 

자 다시 한번만 더 정리.

1. queryFn 함수가 실행되면 서버에서 데이터를 가져오는데, 서버는 사용자 정보가 담긴 데이터를 응답으로 보낸다.

 

2. 쿼리가 성공적으로 완료되면 onSuccess 콜백이 실행되는데, 이 콜백은 쿼리 함수가 반환한 데이터를 인자로 받는다.

(그러니까 서버에서 가져온 데이터가 data 인자로 전달되고, data에는 서버에서 가져온 사용자 정보가 들어있다.)

 

3. dispatch(setUserInfo(data))를 호출해서 가져온 데이터를 리덕스 상태에 저장한다.

 

4. 상태가 업데이트 되면 컴포넌트가 다시 렌더링 되어 최신 사용자 정보를 화면에 표시한다.

 

'리액트' 카테고리의 다른 글

리액트 라우터 사용하기  (0) 2024.06.19
Use Effect가 필요한 이유  (0) 2024.06.18
24. 06. 07 TIL redux-4  (0) 2024.06.07
24. 06. 05 TIL redux-3  (0) 2024.06.05
24. 06. 04 TIL redux-2  (0) 2024.06.04