Data Transformation
[…]
3. using the select option
v3 introduced built-in selectors, which can also be used to transform data:
/* select-transformation */
export const useTodosQuery = () =>
useQuery(['todos'], fetchTodos, {
select: (data) => data.map((todo) => todo.name.toUpperCase()),
})
selectors will only be called if data exists, so you don't have to care about undefined here. Selectors like the one above will also run on every render, because the functional identity changes (it's an inline function). If your transformation is expensive, you can memoize it either with useCallback
, or by extracting it to a stable function reference:
/* select-memoizations */
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: React.useCallback(
(data: Todos) => data.map((todo) => todo.name.toUpperCase()),
[]
),
})
Further, the select option can also be used to subscribe to only parts of the data. This is what makes this approach truly unique. Consider the following example:
/* select-partial-subscriptions */
export const useTodosQuery = (select) =>
useQuery(['todos'], fetchTodos, { select })
export const useTodosCount = () => useTodosQuery((data) => data.length)
export const useTodo = (id) =>
useTodosQuery((data) =>
data.find((todo) => todo.id === id))
Here, we've created a useSelector
like API by passing a custom selector to our useTodosQuery
. The custom hooks still works like before, as select will be undefined if you don't pass it, so the whole state will be returned.
But if you pass a selector, you are now only subscribed to the result of the selector function. This is quite powerful, because it means that even if we update the name of a todo, our component that only subscribes to the count via useTodosCount
will not rerender. The count hasn't changed, so react-query can choose to not inform this observer about the update 🥳 (Please note that this is a bit simplified here and technically not entirely true - I will talk in more detail about render optimizations in Part 3).
- 🟢 best optimizations
- 🟢 allows for partial subscriptions
- 🟡 structure can be different for every observer
- 🟡 structural sharing is performed twice (I will also talk about this in more detail in Part 3)