React Query의 데이터 변환

이재철·2023년 3월 1일
0

react-query

목록 보기
5/12
post-thumbnail

queryFn

queryFn 은 Query를 사용하기 위해 전달되는 함수입니다.
사용자가 Promise를 반환할 것으로 예상하고, 결과 데이터는 query cache로 전환됩니다.

const fetchTodos = async (): Promise<Todos> => {
  const response = await axios.get('todos')
  const data: Todos = response.data

  return data.map((todo) => todo.name.toUpperCase())
}

export const useTodosQuery = () => useQuery(['todos'], fetchTodos)

In the render function

Custom Hook을 작성하여 쉽게 관리하기
➡ Custom Hook에서 데이터 변환과 결합할 수 있는 추가 로직이 있는 경우 좋은 옵션입니다.

const fetchTodos = async (): Promise<Todos> => {
  const response = await axios.get('todos')
  return response.data
}

export const useTodosQuery = () => {
  const queryInfo = useQuery(['todos'], fetchTodos)

  return {
    ...queryInfo,
    data: queryInfo.data?.map((todo) => todo.name.toUpperCase()),
  }
}

🚨 fetch 함수마다 실행되며 실제 모든 render에서 실행됩니다.

  • useMemo 를 사용하여 최적화 할 수 있습니다.
  • queryInfo를 종속성으로 추가하면 변환이 모든 렌더에서 다시 실행됩니다.
  • 데이터는 잠재적으로 정의되지 않을 수 있으므로 optional chaining(?) 을 사용해야합니다.
export const useTodosQuery = () => {
  const queryInfo = useQuery(['todos'], fetchTodos)

  return {
    ...queryInfo,
    // 🚨 don't do this - the useMemo does nothing at all here!
    data: React.useMemo(
      () => queryInfo.data?.map((todo) => todo.name.toUpperCase()),
      [queryInfo]
    ),

    // ✅ correctly memoizes by queryInfo.data
    data: React.useMemo(
      () => queryInfo.data?.map((todo) => todo.name.toUpperCase()),
      [queryInfo.data]
    ),
  }
}

using the select option

데이터 변환에도 사용할 수 있는 built-in 선택자(select)가 있습니다.

export const useTodosQuery = () =>
  useQuery(['todos'], fetchTodos, {
    select: (data) => data.map((todo) => todo.name.toUpperCase()),
  })

선택자 함수 ID가 변경되기 때문에 모든 render 에서 실행됩니다.
💡 변환 비용이 많이 드는 경우 useCallback 을 사용하거나 안정적인 함수로 참조 분리를 할 수 있습니다.

const transformTodoNames = (data: Todos) =>
  data.map((todo) => todo.name.toUpperCase())

export const useTodosQuery = () =>
  useQuery(['todos'], fetchTodos, {
    // ✅ uses a stable function reference
    select: transformTodoNames,
  })

export const useTodosQuery = () =>
  useQuery(['todos'], fetchTodos, {
    // ✅ memoizes with useCallback
    select: useCallback(
      (data: Todos) => data.map((todo) => todo.name.toUpperCase()),
      []
    ),
  })

💡 select 옵션을 사용하여 데이터 일부만 구독할 수 있습니다.

export const useTodosQuery = (select) =>
  useQuery(['todos'], fetchTodos, { select })

// useSelector
export const useTodosCount = () => useTodosQuery((data) => data.length)

export const useTodo = (id) =>
  useTodosQuery((data) => data.find((todo) => todo.id === id))

useTodosQuery 에 Custom Select를 전달하여 useSelector 를 만들 수 있습니다.

  1. 선택자를 주입받지 않은 Custom Hook은 이전과 같이 동작합니다.
    ➡ 전체 상태가 반환
  2. 선택자를 주입받으면 선택자 함수의 결과만 구독
    ➡ Todo의 이름을 업데이트하더라도 오직 useTodosCount를 통해서만 구동하는 Dom 은 다시 render되지 않습니다.
    💡 count가 변경되지 않았으므로 react query는 업데이트에 대해 이 observer 에 알리지 않도록 선택할 수 있습니다.

참조

profile
혼신의 힘을 다하다 🤷‍♂️

0개의 댓글