Frontend/Projects

[React] mbti 테스트 TanStack Query 적용

joycie416 2024. 9. 10. 23:11

어제 이야기한대로 `useState`를 사용하지 않고 테스트 결과가 수정된 경우 반영되도록 바꾸었다.

 

1. `queryClient` 생성하기

먼저 `queryClient`를 생성하고 이를 사용할 수 있도록 `Provider`를 아래와 같이 적용해준다. (`main.jsx`나 `App.jsx`에 하면 된다. 나는 `main.jsx`에 했다.)

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient();

createRoot(document.getElementById('root')).render(
  <QueryClientProvider client={queryClient}>
    <StrictMode>
      <App />
    </StrictMode>
  </QueryClientProvider>,
)

 

2. 필요한 곳에서 `queryClient` 사용하기

이제 위에서 생성한 `queryClient`를 `useQueryClient`를 사용해 불러올 수 있다. (다른 컴포넌트에서 `new QueryClient();`를 한 번 더 하지 않아도 된다는 뜻이다.)

 

그리고 캐싱을 원하는 데이터를 `useQuery`를 통해 저장할 수 있다. 이 때 `queryKey`와 `queryFn`은 필수다. `queryKey`는 말 그대로 해당 데이터를 부를 이름 같은 것이고, `queryFn`은 데이터를 불러올 함수이다.

import React from 'react'
import { getTestResults, updateTestResultVisibility } from '../axios/testResults';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

const ResultPage = () => {
  const queryClient = useQueryClient();

  const fetchResults = async () => {
    const data = await getTestResults();
    return data;
  };

  const { data: results, isPending, isError } = useQuery({
    queryKey: ['results'],
    queryFn: fetchResults,
    select: value => value.filter(ele => ele.visibility)
  })
  
  ...

  return (
    ...
  )
}

export default ResultPage

 

`select`는 불러온 데이터를 원하는대로 가공할 수 있게 해준다. (공식 문서를 보면 최적화를 위해 `select`에 사용하는 함수를 `useCallback`으로 감싸는 것을 추천하고 있다.) 나는 모든 결과 중에서 '공개'로 설정된 결과만 보여주기 위해 위 함수를 사용했다.

 

3. 데이터 변경하기

`useMutation`을 사용해 변형을 가할 수 있다. `mutationFn`에 사용할 함수를 입력하면 반환된 객체 중 `mutate`를 통해 사용할 수 있다. `onSuccess`는 `mutationFn`이 성공적으로 작동한 경우 작동하는 함수이며, 기본적으로 `mutationFn`의 결과값을 입력값으로 받는다.

import React from 'react'
import { getTestResults, updateTestResultVisibility } from '../axios/testResults';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

const ResultPage = () => {
  const queryClient = useQueryClient();

  const fetchResults = async () => {
    const data = await getTestResults();
    return data;
  };

  const { data: results, isPending, isError } = useQuery({
    queryKey: ['results'],
    queryFn: fetchResults,
    select: value => value.filter(ele => ele.visibility)
  })

  const editVisibility = async (result) => {
    console.log('change visibility :', result);
    await updateTestResultVisibility(result.id, result.visibility);
  }

  const { mutate:update } = useMutation({
    mutationFn: editVisibility,
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['results']
      })
    }
  })

  const handleUpdate = (result) => {
    return (e) => {
      e.stopPropagation();
      update(result);
      }
  };

  return (
    ...
  )
}

export default ResultPage

 

나는 `나만보기` 버튼을 누르면 '비공개'로 전환되는 기능을 수행하고 싶었으므로 `editVisibility`를 `mutationFn`으로 사용했고, 성공한 경우 데이터를 다시 불러오게 하기 위해 `results`에 대응하는 데이터를 무효화했다.

 

원래는 `editVisibility` 함수를 동기 함수로 작성했는데, 변화가 즉각적으로 보이지 않았다. 비동기 함수로 만드니 `나만보기`에서 `공개하기`로 버튼의 내용이 바뀌는 것을 확인할 수 있었다. 동기 함수로 설정한 경우, 비동기 함수인 `updateTestResultVisibility`가 `fulfilled`가 되기 전에 지나가버려 실행 결과가 '성공'으로 판별되지 않기 때문인 것 같다.

 

위에 사용한 hook들에는 더 많은 옵션들이 있으니 아래 공식 문서를 확인하는 것을 추천한다.


이제 남은 건 로그인 상태에서 새로고침했을 때 메인페이지로 넘어가지 않도록 수정하는 것이다. 지금 로직은 전역 상태로 저장한 `user` 정보가 새로고침 직후에 `null`이라서 메인페이지로 넘어가게 된다. 이를 적절히 수정해야 한다.