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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(QueryObserver): track queries as default #2987

Merged
merged 13 commits into from Nov 20, 2021
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
2 changes: 1 addition & 1 deletion docs/src/pages/comparison.md
Expand Up @@ -64,7 +64,7 @@ Feature/Capability Key:

> **<sup>1</sup> Lagged Query Data** - React Query provides a way to continue to see an existing query's data while the next query loads (similar to the same UX that suspense will soon provide natively). This is extremely important when writing pagination UIs or infinite loading UIs where you do not want to show a hard loading state whenever a new query is requested. Other libraries do not have this capability and render a hard loading state for the new query (unless it has been prefetched), while the new query loads.

> **<sup>2</sup> Render Optimization** - React Query has excellent rendering performance. It will only re-render your components when a query is updated. For example because it has new data, or to indicate it is fetching. React Query also batches updates together to make sure your application only re-renders once when multiple components are using the same query. If you are only interested in the `data` or `error` properties, you can reduce the number of renders even more by setting `notifyOnChangeProps` to `['data', 'error']`. Set `notifyOnChangeProps: 'tracked'` to automatically track which fields are accessed and only re-render if one of them changes.
> **<sup>2</sup> Render Optimization** - React Query has excellent rendering performance. By default, it will automatically track which fields are accessed and only re-render if one of them changes. If you would like to opt-out of this optimization, setting `notifyOnChangeProps` to `'all'` will re-render your components whenever the query is updated. For example because it has new data, or to indicate it is fetching. React Query also batches updates together to make sure your application only re-renders once when multiple components are using the same query. If you are only interested in the `data` or `error` properties, you can reduce the number of renders even more by setting `notifyOnChangeProps` to `['data', 'error']`.

> **<sup>3</sup> Partial query matching** - Because React Query uses deterministic query key serialization, this allows you to manipulate variable groups of queries without having to know each individual query-key that you want to match, eg. you can refetch every query that starts with `todos` in its key, regardless of variables, or you can target specific queries with (or without) variables or nested properties, and even use a filter function to only match queries that pass your specific conditions.

Expand Down
10 changes: 10 additions & 0 deletions docs/src/pages/guides/migrating-to-react-query-4.md
Expand Up @@ -27,6 +27,16 @@ With version [3.22.0](https://github.com/tannerlinsley/react-query/releases/tag/
+ import { dehydrate, hydrate, useHydrate, Hydrate } from 'react-query'
```

### `notifyOnChangeProps` property no longer accepts `"tracked"` as a value

The `notifyOnChangeProps` option no longer accepts a `"tracked"` value. Instead, `useQuery` defaults to tracking properties. All queries using `notifyOnChangeProps: "tracked"` should be updated by removing this option.

If you would like to bypass this in any queries to emulate the v3 default behavior of re-rendering whenever a query changes, `notifyOnChangeProps` now accepts an `"all"` value to opt-out of the default smart tracking optimization.

### `notifyOnChangePropsExclusion` has been removed

In v4, `notifyOnChangeProps` defaults to the `"tracked"` behavior of v3 instead of `undefined`. Now that `"tracked"` is the default behavior for v4, it no longer makes sense to include this config option.

### Consistent behavior for `cancelRefetch`

The `cancelRefetch` can be passed to all functions that imperatively fetch a query, namely:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/reference/QueryClient.md
Expand Up @@ -91,7 +91,7 @@ try {

**Options**

The options for `fetchQuery` are exactly the same as those of [`useQuery`](./useQuery), except the following: `enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, notifyOnChangeProps, notifyOnChangePropsExclusions, onSuccess, onError, onSettled, useErrorBoundary, select, suspense, keepPreviousData, placeholderData`; which are strictly for useQuery and useInfiniteQuery. You can check the [source code](https://github.com/tannerlinsley/react-query/blob/361935a12cec6f36d0bd6ba12e84136c405047c5/src/core/types.ts#L83) for more clarity.
The options for `fetchQuery` are exactly the same as those of [`useQuery`](./useQuery), except the following: `enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, notifyOnChangeProps, onSuccess, onError, onSettled, useErrorBoundary, select, suspense, keepPreviousData, placeholderData`; which are strictly for useQuery and useInfiniteQuery. You can check the [source code](https://github.com/tannerlinsley/react-query/blob/361935a12cec6f36d0bd6ba12e84136c405047c5/src/core/types.ts#L83) for more clarity.

**Returns**

Expand Down
10 changes: 3 additions & 7 deletions docs/src/pages/reference/useQuery.md
Expand Up @@ -35,7 +35,6 @@ const {
keepPreviousData,
meta,
notifyOnChangeProps,
notifyOnChangePropsExclusions,
onError,
onSettled,
onSuccess,
Expand Down Expand Up @@ -125,15 +124,12 @@ const result = useQuery({
- If set to `true`, the query will refetch on reconnect if the data is stale.
- If set to `false`, the query will not refetch on reconnect.
- If set to `"always"`, the query will always refetch on reconnect.
- `notifyOnChangeProps: string[] | "tracked"`
- `notifyOnChangeProps: string[] | "all"`
- Optional
- If set, the component will only re-render if any of the listed properties change.
- If set to `['data', 'error']` for example, the component will only re-render when the `data` or `error` properties change.
- If set to `"tracked"`, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.
- `notifyOnChangePropsExclusions: string[]`
- Optional
- If set, the component will not re-render if any of the listed properties change.
- If set to `['isStale']` for example, the component will not re-render when the `isStale` property changes.
- If set to `"all"`, the component will opt-out of smart tracking and re-render whenever a query is updated.
- By default, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.
- `onSuccess: (data: TData) => void`
- Optional
- This function will fire any time the query successfully fetches new data.
Expand Down
19 changes: 7 additions & 12 deletions src/core/queryObserver.ts
Expand Up @@ -610,27 +610,22 @@ export class QueryObserver<
return true
}

const { notifyOnChangeProps, notifyOnChangePropsExclusions } = this.options
const { notifyOnChangeProps } = this.options

if (!notifyOnChangeProps && !notifyOnChangePropsExclusions) {
return true
}

if (notifyOnChangeProps === 'tracked' && !this.trackedProps.length) {
if (
notifyOnChangeProps === 'all' ||
(!notifyOnChangeProps && !this.trackedProps.length)
) {
return true
}

const includedProps =
notifyOnChangeProps === 'tracked'
? this.trackedProps
: notifyOnChangeProps
const includedProps = notifyOnChangeProps ?? this.trackedProps

return Object.keys(result).some(key => {
const typedKey = key as keyof QueryObserverResult
const changed = result[typedKey] !== prevResult[typedKey]
const isIncluded = includedProps?.some(x => x === key)
const isExcluded = notifyOnChangePropsExclusions?.some(x => x === key)
return changed && !isExcluded && (!includedProps || isIncluded)
return changed && (!includedProps || isIncluded)
})
}

Expand Down
9 changes: 3 additions & 6 deletions src/core/types.ts
Expand Up @@ -154,13 +154,10 @@ export interface QueryObserverOptions<
/**
* If set, the component will only re-render if any of the listed properties change.
* When set to `['data', 'error']`, the component will only re-render when the `data` or `error` properties change.
* When set to `tracked`, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.
* When set to `'all'`, the component will re-render whenever a query is updated.
* By default, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.
*/
notifyOnChangeProps?: Array<keyof InfiniteQueryObserverResult> | 'tracked'
/**
* If set, the component will not re-render if any of the listed properties change.
*/
notifyOnChangePropsExclusions?: Array<keyof InfiniteQueryObserverResult>
notifyOnChangeProps?: Array<keyof InfiniteQueryObserverResult> | 'all'
/**
* This callback will fire any time the query successfully fetches new data or the cache is updated via `setQueryData`.
*/
Expand Down
1 change: 0 additions & 1 deletion src/reactjs/tests/QueryResetErrorBoundary.test.tsx
Expand Up @@ -613,7 +613,6 @@ describe('QueryErrorResetBoundary', () => {
{
retry: false,
useErrorBoundary: true,
notifyOnChangeProps: 'tracked',
}
)
return <div>{data}</div>
Expand Down
16 changes: 14 additions & 2 deletions src/reactjs/tests/useInfiniteQuery.test.tsx
Expand Up @@ -186,6 +186,7 @@ describe('useInfiniteQuery', () => {
{
getNextPageParam: () => 1,
keepPreviousData: true,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -306,6 +307,7 @@ describe('useInfiniteQuery', () => {
pages: [...data.pages].reverse(),
pageParams: [...data.pageParams].reverse(),
}),
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -359,6 +361,7 @@ describe('useInfiniteQuery', () => {
},
{
getPreviousPageParam: firstPage => firstPage - 1,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -423,8 +426,10 @@ describe('useInfiniteQuery', () => {
const states: UseInfiniteQueryResult<number>[] = []

function Page() {
const state = useInfiniteQuery(key, ({ pageParam = 10 }) =>
Number(pageParam)
const state = useInfiniteQuery(
key,
({ pageParam = 10 }) => Number(pageParam),
{ notifyOnChangeProps: 'all' }
)

states.push(state)
Expand Down Expand Up @@ -516,6 +521,7 @@ describe('useInfiniteQuery', () => {
{
getPreviousPageParam: firstPage => firstPage - 1,
getNextPageParam: lastPage => lastPage + 1,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -608,6 +614,7 @@ describe('useInfiniteQuery', () => {
({ pageParam = 10 }) => Number(pageParam) * multiplier.current,
{
getNextPageParam: lastPage => lastPage + 1,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -687,6 +694,7 @@ describe('useInfiniteQuery', () => {
},
{
getNextPageParam: lastPage => lastPage + 1,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -913,6 +921,7 @@ describe('useInfiniteQuery', () => {
},
{
getNextPageParam: lastPage => lastPage + 1,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -1014,6 +1023,7 @@ describe('useInfiniteQuery', () => {
},
{
getNextPageParam: lastPage => lastPage + 1,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -1080,6 +1090,7 @@ describe('useInfiniteQuery', () => {
},
{
getNextPageParam: lastPage => lastPage + 1,
notifyOnChangeProps: 'all',
}
)

Expand Down Expand Up @@ -1169,6 +1180,7 @@ describe('useInfiniteQuery', () => {
{
initialData: { pages: [1], pageParams: [1] },
getNextPageParam: lastPage => lastPage + 1,
notifyOnChangeProps: 'all',
}
)

Expand Down