ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 냅다 시작하는 리액트 쿼리 (개념편)
    React.js, Next.js 2022. 12. 31. 19:44

    들어가며

    대부분의 예제는 이전에 작성한 글의 함수 및 흐름을 그대로 사용하였습니다.

     

    냅다 시작하는 리액트 쿼리 (예제편)

    냅다 시작하는 리액트 쿼리 (예제편) $ npx create-next-app test --typescript $ yarn add axios @tanstack/react-query _app.tsx // 🗂/pages/_app.tsx import React from 'react'; import type { AppProps } from 'next/app'; import { QueryClient, Query

    junheedot.tistory.com

    목차

    • Query Basics
    • Query Keys
    • Query Functions
    • Query Client

    Query Basics

    쿼리는 쿼리의 고유키에 연결된 비동기 데이터 소스에 대한 선언적인 의존성입니다. 쿼리는 서버에서 데이터를 가져오기 위해 모든 Promise 기반 메서드(GET 및 POST 메서드 포함)와 함께 사용할 수 있습니다.

    너의 컴포넌트 또는 커스텀 훅에 대한 쿼리를 구독하기 위해서는 useQuery 훅을 호출해야 합니다:

    • 쿼리를 위한 유니크 키
    • 함수는 프로미스를 반환합니다:
      • resolve the data
      • thorws an error
    import { useQuery } from '@tanstack/react-query'
    
    function App() {
      const info = useQuery(['todos'], fetchTodoList)
    }
    

    제공받은 유니크 키는 내부적으로 어플리케이션 전체에서 refetching, caching, sharing 하기 위해 사용됩니다.

    쿼리의 결과값은 useQuery의 결과값으로 반환됩니다. useQuery 에는 템플릿 및 기타 데이터 사용에 필요한 쿼리에 대한 모든 정보가 포함되어 있습니다

    const result = useQuery(['todos'], fetchTodoList)
    

    result 객체에는 생산적이기 위해 인식해야 하는 중요한 states 들이 있습니다. 쿼리는 주어지는 moment에 위해서만 값을 가질 수 있습니다.

    • isLoading or status === 'loading' - The query has no data yet
    • isError or status === 'error' - The query encountered an error
    • isSuccess or status === 'success' - The query was successful and data is available
    export declare type QueryStatus = 'loading' | 'error' | 'success';
    

    이러한 기본 상태 외에도 쿼리 상태에 따라 더 많은 정보를 사용할 수 있습니다.

    • error - If the query is in an isError state, the error is available via the error property.
    • data - If the query is in a success state, the data is available via the data property.

    대부분의 쿼리의 경우 일반적으로 isLoading상태를 확인한 다음 상태 를 확인한 isError다음 마지막으로 데이터를 사용할 수 있다고 가정하고 성공적인 상태를 렌더링 하는 것으로 충분합니다.

    function Todos() {
      const { isLoading, isError, data, error } = useQuery(['todos'], fetchTodoList)
      if (isLoading) {
        return <span>Loading...</span>
      }
      if (isError) {
        return <span>Error: {error.message}</span>
      }
    // We can assume by this point that `isSuccess === true`
      return (
        <ul>
          {data.map(todo => (
            <li key={todo.id}>{todo.title}</li>
          ))}
        </ul>
      )
    }
    

    불리언 값이 마음에 들지 않는다면 status 상태 또한 사용할 수 있습니다.

    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>
      }
    
      // also status === 'success', but "else" logic works, too
      return (
        <ul>
          {data.map(todo => (
            <li key={todo.id}>{todo.title}</li>
          ))}
        </ul>
      )
    }
    

    FetchStatus

    status 필드인 객체 외에도 다음 옵션이 포함 result 된 추가 속성을 얻을 수 있습니다:

    • fetchStatus === 'fetching' - The query is currently fetching
    • fetchStatus === 'paused' - The query wanted to fetch, but it is paused
    • fetchStatus === 'idle' - The query is not doing anything at the moment
    import { useQuery } from '@tanstack/react-query';
    

    Query Keys

    React Query는 쿼리 키를 기반으로 쿼리 캐싱을 관리합니다. 쿼리 키는 최상위 수준에서 배열이어야 하며 단일 문자열이 있는 배열처럼 단순할 수도 있고 많은 문자열과 중첩된 개체의 배열처럼 복잡할 수도 있습니다. 쿼리키가 직렬화(sequelize) 가능하고 쿼리의 데이터로써 유니크하다면 쿼리 키를 사용할 수 있습니다!

    1. Simple Query Keys

    키의 가장 간단한 형태는 상수 값이 있는 배열입니다:

    • Generic List/Index resources
    • Non-hierarchical resources
    // A list of todos
    useQuery(['todos'], ...)
    
    // Something else, whatever!
    useQuery(['something', 'special'], ...)
    

    2. Array Keys with variables

    쿼리에 데이터를 고유하게 설명하기 위해 추가 정보가 필요한 경우 문자열이 있는 배열과 직렬화 가능한 개체를 사용하여 설명할 수 있습니다. 다음과 같은 경우에 유용합니다.

    • Hierarchical or nested resources
      • It's common to pass an ID, index, or other primitive to uniquely identify the item
    • Queries with additional parameters
      • It's common to pass an object of additional options
    // An individual todo
    useQuery(['todo', 5], ...)
    
    // An individual todo in a "preview" format
    useQuery(['todo', 5, { preview: true }], ...)
    
    // A list of todos that are "done"
    useQuery(['todos', { type: 'done' }], ...)
    

    3. Query Keys are hashed deterministically!

    쿼리 키는 결정적으로 해시됩니다. 이것은 객체의 키 순서에 관계 없이 다음 쿼리는 모두 동일한 것으로 간주한다는 것을 의미합니다.

    useQuery(['todos', { status, page }], ...)
    useQuery(['todos', { page, status }], ...)
    useQuery(['todos', { page, status, other: undefined }], ...)
    

    그러나 다음 쿼리 키는 같지 않습니다. 배열 항목 순서가 중요합니다!

    useQuery(['todos', status, page], ...)
    useQuery(['todos', page, status], ...)
    useQuery(['todos', undefined, page, status], ...)
    

    4. If your query function depends on a variable, include it in your query key

    쿼리 키는 가져오는 데이터를 고유하게 설명하므로 쿼리 함수에서 변경 하는 모든 변수를 포함해야 합니다.

    function Todos({ todoId }) {
      const result = useQuery(['todos', todoId], () => fetchTodoById(todoId))
    }
    

    Query Functions

    쿼리 함수는 말 그대로 Promise를 반환하는 모든 함수일 수 있습니다. 반환되는 프로미스는 데이터를 resolve(return) 하거나 오류를 발생(throw) 시켜야 합니다 .

    useQuery(['todos'], fetchAllTodos)
    
    useQuery(['todos', todoId], () => fetchTodoById(todoId))
    
    useQuery(['todos', todoId], async () => {
      const data = await fetchTodoById(todoId)
      return data
    })
    
    useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]))
    

    1. Handling and Throwing Errors

    React Query가 쿼리에 오류가 있다고 판단하려면 쿼리 함수를 throw 해야 합니다. 쿼리 함수에서 발생한 모든 오류는 쿼리 error 상태에서 지속됩니다.

    const { ..., error } = useQuery(['todos', todoId], async () => {
      if (somethingGoesWrong) {
        throw new Error('Oh no!')
      }
    
      return data
    })
    

    2. Usage with ‘fetch’ and other clients that do not throw by default

    ‘axios’ 또는 ‘graphql-request’ 의 경우 자동으로 성공하지 않은 HTTP 호출에 대한 에러를 반환하지만 ‘fetch’ 메서드의 경우 기본적으로 에러를 반환하지 않습니다. 따라서 이러한 경우에는 사용자 스스로 에러를 던져줘야 합니다.

    • axios (unsuccessful HTTP 호출에 대한 에러를 자동 반환)
    useQuery(['todos', todoId], async () => {
      const response = await axios.get('/todos/' + todoId)
    
      return response.data
    })
    
    • fetch (unsuccessful HTTP 호출에 대한 에러를 자동 반환하지 않음)
    useQuery(['todos', todoId], async () => {
      const response = await fetch('/todos/' + todoId)
      if (!response.ok) {
        throw new Error('Network response was not ok')
      }
      return response.json()
    })
    

    3. Using a Query Object instead of parameters

    React Query의 API 전체에서 서명이 지원되는 곳이면 어디에서나 **[queryKey, queryFn, config]**객체를 사용하여 동일한 구성을 표현할 수도 있습니다.

    import { useQuery } from '@tanstack/react-query'
    
    useQuery({
      queryKey: ['todo', 7],
      queryFn: fetchTodo,
      ...config,
    })
    

    case 1: queryKey, queryFn 등을 파라미터로 바로 명시하는 방법

    const useMovies = (name: string) => {
      return useQuery([`${name}`], () => fetchMovies(name));
    };
    

    case 2: options에 객체로 넣어주는 방법

    const useMovies = (name: string) => {
      return useQuery({
        queryKey: [`${name}`],
        queryFn: () => fetchMovies(name),
      });
    };
    

    Query Client

    QueryClient는 react-query를 사용하기 위해 사용하는 생성자 함수로 다양한 내부 메서드 및 프로퍼티를 가지고 있습니다.

    import { QueryClient } from 'react-query'
    
    const queryClient = new QueryClient()
    

    staleTime

    import { QueryClient } from 'react-query'
     
     const queryClient = new QueryClient({
       defaultOptions: {
         queries: {
           staleTime: Infinity,
         },
       },
     })
     
     await queryClient.prefetchQuery('posts', fetchPosts)
    

    Window Focuse Refetching

    리액트 쿼리는 기본적으로 윈도우 창에 포커스 될 때 데이터를 새로 불러옵니다. 본인의 상황에 맞춰 해당 기능을 사용할지 정할 수 있습니다. 글로벌하게 적용할 경우와 각 쿼리별 적용할 경우로 나눌 수 있습니다. 기본 값은 false입니다.

     

    • Disabling Globally
    //
     const queryClient = new QueryClient({
       defaultOptions: {
         queries: {
           refetchOnWindowFocus: false,
         },
       },
     })
     
     function App() {
       return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
     }
    
    • Disabling Per-Query
     useQuery('todos', fetchTodos, { refetchOnWindowFocus: false })
    

    Query Retries

    쿼리 요청이 실패했을 때 리액트 쿼리는 자동적으로 쿼리를 재시도합니다. 초기값은 3이며, 시도 횟수를 설정할 수 있습니다.

    Set the retry cnt

    import { useQuery } from 'react-query'
     
     // Make a specific query retry a certain number of times
     const result = useQuery(['todos', 1], fetchTodoListPage, {
       retry: 10, // Will retry failed requests 10 times before displaying an error
     })
    
    • fetchMovies에서 패칭 이후 일부러 에러를 발생시켰습니다
    • retry 횟수를 3회로 줄였습니다
    const fetchMovies = async (name: string) => {
      const response = await axios.get(
        `https://api.themoviedb.org/3/search/movie?api_key=${REACT_APP_API_KEY}&language=en-US&query=${name}`
      );
    
      throw new Error('test!');
      return response.data.results;
    };
    
    const useMovies = (name: string) => {
      return useQuery({
        queryKey: [`${name}`],
        queryFn: () => fetchMovies(name),
        retry: 3,
      });
    };
    
    import React from 'react';
    import { useMovies } from '@hooks';
    
    const ReactQueryComponent = () => {
      const {
        isSuccess: movieIsSuccess,
        isLoading: movieIsLoading,
        isError: movieIsError,
        data: movieData,
        error: movieError,
      } = useMovies('spiderman');
    
      if (movieIsLoading) {
        return <span>Loading...</span>;
      }
    
      if (movieIsError) {
        console.warn('error', movieError);
        return <span>Error... </span>;
      }
    
      if (movieIsSuccess && movieData.length === 0) {
        return <span>There is no data...</span>;
      }
    
      return (
        <div>
          <h1>Hello React Query!</h1>
    
          {movieData.slice(0, 5).map((item: any) => (
            <div key={item.id}>{item.title}</div>
          ))}
        </div>
      );
    };
    
    export default ReactQueryComponent;
    

     

     

    Retry Delay

    • retry 요청 간의 딜레이를 설정할 수 있습니다
    const result = useQuery('todos', fetchTodoList, {
       retryDelay: 1000, // Will always wait 1000ms to retry, regardless of how many retries
     })
    

    댓글

Designed by Tistory.