- Mar 2023
-
tkdodo.eu tkdodo.eu
Tags
Annotators
URL
-
-
codesandbox.io codesandbox.io
Tags
Annotators
URL
-
-
tanstack.com tanstack.com
-
dev.to dev.to
-
tanstack.com tanstack.com
Tags
Annotators
URL
-
-
react-query-v3.tanstack.com react-query-v3.tanstack.comSSR1
Tags
Annotators
URL
-
- Feb 2023
- Dec 2022
- Oct 2022
-
reactrouter.com reactrouter.com
- Sep 2022
-
tkdodo.eu tkdodo.eu
- Aug 2022
-
blog.openreplay.com blog.openreplay.com
-
www.youtube.com www.youtube.com
-
-
Tags
Annotators
URL
-
-
daily-dev-tips.com daily-dev-tips.com
-
And in our case, we want it to keep track of our storage object. So let's also create a usePeristentContext hook.
```js import { useMutation, useQuery, useQueryClient } from 'react-query';
export default function usePersistentContext(key) { const queryClient = useQueryClient();
const { data } = useQuery(key, () => localStorage.getItem(key));
const { mutateAsync: setValue } = useMutation( (value) => localStorage.setItem(key, value), { onMutate: (mutatedData) => { const current = data; queryClient.setQueryData(key, mutatedData); return current; }, onError: (_, __, rollback) => { queryClient.setQueryData(key, rollback); }, } );
return [data, setValue]; } ```
js function SetContext() { const [value, setValue] = usePersistentContext('item_context'); return ( <button onClick={() => setValue(value === 'on' ? 'off' : 'on')}> Click me {value} </button> ); }
-
-
www.youtube.com www.youtube.comYouTube1
-
- Jul 2022
-
tanstack.com tanstack.com
- Jun 2022
-
codesandbox.io codesandbox.io
Tags
Annotators
URL
-
-
dev.to dev.to
-
www.smashingmagazine.com www.smashingmagazine.com
- Feb 2022
-
github.com github.com
-
js queryClient .getQueryCache() .findAll(['partial', 'key']) .forEach(({ queryKey }) => { queryClient.setQueryData(queryKey, newData) })
-
-
dhruvnakum.xyz dhruvnakum.xyz
-
www.carlrippon.com www.carlrippon.com
-
www.smashingmagazine.com www.smashingmagazine.com
-
However there are some restrictions: if you are going to use different URLs for GET/PATCH, for example, you have to use the same key, otherwise, React Query will not be able to match these queries.
It is ok to use url as key for React query
-
- Jan 2022
-
lukesmurray.com lukesmurray.com
-
www.bezkoder.com www.bezkoder.com
Tags
Annotators
URL
-
-
egreb.net egreb.net
-
codesandbox.io codesandbox.io
-
betterprogramming.pub betterprogramming.pub
-
tkdodo.eu tkdodo.eu
-
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 ouruseTodosQuery
. 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)
-
Data Transformation
[...]
2. In the render function
As advised in Part 1, if you create custom hooks, you can easily do transformations there:
/* render-transformation */ 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()), } }
As it stands, this will not only run every time your fetch function runs, but actually on every render (even those that do not involve data fetching). This is likely not a problem at all, but if it is, you can optimize with
useMemo
. Be careful to define your dependencies as narrow as possible. data inside thequeryInfo
will be referentially stable unless something really changed (in which case you want to recompute your transformation), but thequeryInfo
itself will not. If you addqueryInfo
as your dependency, the transformation will again run on every render:/* useMemo-dependencies */ 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] ), } }
Especially if you have additional logic in your custom hook to combine with your data transformation, this is a good option. Be aware that data can be potentially undefined, so use optional chaining when working with it.
- 🟢 optimizable via useMemo
- 🟡 exact structure cannot be inspected in the devtools
- 🔴 a bit more convoluted syntax
- 🔴 data can be potentially undefined
-
-
www.sitepoint.com www.sitepoint.com
Tags
Annotators
URL
-
-
-
🤔 So what?
Let’s review a typical Redux process for a fetch operation:
- Dispatch an Action
fetchSomething()
from within the Component - This Action hits the Reducer which will update the store with something like
isLoading: true
- The Component re-renders and displays a loader
- In the meanwhile the Action hits the Middleware which takes care of calling the API
- The API returns the response to the Middleware, which dispatches another Action:
fetchSomethingSucceeded()
when succeeded, orfetchSomethingFailed()
when failed - The succeeded/failed Action hits the Reducer, which updates the
store
- Finally, the response is back to the Component (optionally through a memoized selector) which will show the data
It’s a long process for just a simple fetch, isn’t it?
- Dispatch an Action
-
-
tkdodo.eu tkdodo.eu
- Dec 2021
-
tkdodo.eu tkdodo.eu
Tags
Annotators
URL
-
-
tkdodo.eu tkdodo.eu
Tags
Annotators
URL
-
-
tkdodo.eu tkdodo.eu
-
Increasing StaleTime
React Query comes with a default staleTime of zero. This means that every query will be immediately considered as stale, which means it will refetch when a new subscriber mounts or when the user refocuses the window. It is aimed to keep your data as up-to-date as necessary.
This goal overlaps a lot with WebSockets, which update your data in real-time. Why would I need to refetch at all if I just manually invalidated because the server just told me to do so via a dedicated message?
So if you update all your data via websockets anyways, consider setting a high
staleTime
. In my example, I just usedInfinity
. This means the data will be fetched initially via useQuery, and then always come from the cache. Refetching only happens via the explicit query invalidation.You can best achieve this by setting global query defaults when creating the QueryClient:
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: Infinity, }, }, })
-
-
codesandbox.io codesandbox.io
Tags
Annotators
URL
-
-
thewebdev.info thewebdev.info
-
react-query.tanstack.com react-query.tanstack.com
-
blog.theodo.com blog.theodo.com
- Aug 2021
-
blog.isquaredsoftware.com blog.isquaredsoftware.com
-
We can even say that server caching tools like React-Query, SWR, Apollo, and Urql fit the definition of "state management" - they store initial values based on the fetched data, return the current value via their hooks, allow updates via "server mutations", and notify of changes via re-rendering the component
-