Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: render optimizations #7234

Merged
merged 3 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@
"label": "Caching",
"to": "framework/react/guides/caching"
},
{
"label": "Render Optimizations",
"to": "framework/react/guides/render-optimizations"
},
{
"label": "Default Query Fn",
"to": "framework/react/guides/default-query-function"
Expand Down
74 changes: 74 additions & 0 deletions docs/framework/react/guides/render-optimizations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
id: render-optimizations
title: Render Optimizations
---

React Query applies a couple of optimizations automatically to ensure that your components only re-render when they actually need to. This is done by the following means:

## structural sharing

React Query uses a technique called "structural sharing" to ensure that as many references as possible will be kept intact between re-renders. If data is fetched over the network, usually, you'll get a completely new reference by json parsing the response. However, React Query will keep the original reference if _nothing_ changed in the data. If a subset changed, React Query will keep the unchanged parts and only replace the changed parts.

> Note: This optimization only works if the `queryFn` returns JSON compatible data. You can turn it off by setting `structuralSharing: false` globally or on a per-query basis, or you can implement your own structural sharing by passing a function to it.

### referential identity

The top level object returned from `useQuery`, `useInfinitQuery`, `useMutation` and the Array returned from `useQueries` is **not referentially stable**. It will be new a new reference on every render. However, the `data` properties returned from these hooks will be as stable as possible.

## tracked properties

React Query will only trigger a re-render if one of the properties returned from `useQuery` is actually "used". This is done by using [custom getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#custom_setters_and_getters). This avoids a lot of unnecessary re-renders, e.g. because properties like `isFetching` or `isStale` might change often, but are not used in the component.

You can customize this feature by setting `notifyOnChangeProps` manually globally or on a per-query basis. If you want to turn that feature off, you can set `notifyOnChangeProps: 'all'`.

> Note: Custom getters are invoked by accessing a property, either via destructuring or by accessing it directly. If you use object rest destructuring, you will disable this optimization. We have a [lint rule](../../../eslint/no-rest-destructuring) to guard against this pitfall.

## select

You can use the `select` option to select a subset of the data that your component should subscribe to. This is useful for highly optimized data transformations or to avoid unnecessary re-renders.

```js
export const useTodos = (select) => {
return useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
select,
})
}

export const useTodoCount = () => {
return useTodos((data) => data.length)
}
```

A component using the `useTodoCount` custom hook will only re-render if the length of the todos changes. It will **not** re-render if e.g. the name of a todo changed.

### memoization

The `select` function will only re-run if:

- the `select` function itself changed referentially
- `data` changed

This means that an inlined `select` function, as shown above, will run on every render. To avoid this, you can wrap the `select` function in `useCallback`, or extract it so a stable function reference if it doesn't have any dependencies:

```js
// wrapped in useCallback
export const useTodoCount = () => {
return useTodos(useCallback((data) => data.length, []))
}
```

```js
// extracted to a stable function reference
const selectTodoCount = (data) => data.length

export const useTodoCount = () => {
return useTodos(selectTodoCount)
}
```

## Further Reading

For an in-depth guide about these topics, read [React Query Render Optimizations](../tkdodos-blog#3-react-query-render-optimizations) from
the Community Resources.
1 change: 1 addition & 0 deletions docs/framework/react/reference/useQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ const {
- `select: (data: TData) => unknown`
- Optional
- This option can be used to transform or select a part of the data returned by the query function. It affects the returned `data` value, but does not affect what gets stored in the query cache.
- The `select` function will only run if `data` changed, or if the reference to the `select` function itself changes. To optimize, wrap the function in `useCallback`.
- `initialData: TData | () => TData`
- Optional
- If set, this value will be used as the initial data for the query cache (as long as the query hasn't been created or cached yet)
Expand Down