Skip to content

Commit 46a7346

Browse files
committed
fix: make sure component re-render when they should
1 parent 99951a0 commit 46a7346

File tree

6 files changed

+78
-17
lines changed

6 files changed

+78
-17
lines changed

docs/src/pages/comparison.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Feature/Capability Key:
5555

5656
> **<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.
5757
58-
> **<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 `notifyOnStatusChange` to `false`.
58+
> **<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']`.
5959
6060
> **<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.
6161

docs/src/pages/guides/migrating-to-react-query-3.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,38 @@ The `queryFnParamsFilter` option has been removed because query functions now ge
320320
321321
Parameters can still be filtered within the query function itself as the `QueryFunctionContext` also contains the query key.
322322
323+
### QueryOptions.notifyOnStatusChange
324+
325+
The `notifyOnStatusChange` option has been replaced by the `notifyOnChangeProps` and `notifyOnChangePropsExclusions` props.
326+
327+
With these options it is possible to configure when a component should re-render on a granular level.
328+
329+
Only re-render when the `data` or `error` properties change:
330+
331+
```js
332+
import { useQuery } from 'react-query'
333+
334+
function User() {
335+
const { data } = useQuery('user', fetchUser, {
336+
notifyOnChangeProps: ['data', 'error'],
337+
})
338+
return <div>Username: {data.username}</div>
339+
}
340+
```
341+
342+
Prevent re-render when the `isStale` property changes:
343+
344+
```js
345+
import { useQuery } from 'react-query'
346+
347+
function User() {
348+
const { data } = useQuery('user', fetchUser, {
349+
notifyOnChangePropsExclusions: ['isStale'],
350+
})
351+
return <div>Username: {data.username}</div>
352+
}
353+
```
354+
323355
### QueryResult.clear()
324356
325357
The `QueryResult.clear()` method has been renamed to `QueryResult.remove()`.
@@ -379,7 +411,7 @@ function User() {
379411
}
380412
```
381413
382-
Set the `notifyOnStatusChange` option to `false` to only re-render when the selected data changes.
414+
Set the `notifyOnChangeProps` option to `['data', 'error']` to only re-render when the selected data changes.
383415
384416
#### useQueries()
385417

src/core/queryObserver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ export class QueryObserver<
448448

449449
if (changed) {
450450
if (notifyOnChangePropsExclusions && isExcluded) {
451-
break
451+
continue
452452
}
453453

454454
if (!notifyOnChangeProps || isIncluded) {

src/core/tests/queryCache.test.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -612,10 +612,7 @@ describe('queryCache', () => {
612612
const key = queryKey()
613613
const queryFn = jest.fn()
614614
const testCache = new QueryCache()
615-
const testClient = new QueryClient({
616-
queryCache: testCache,
617-
defaultOptions: { queries: { notifyOnChangePropsExclusions: [] } },
618-
})
615+
const testClient = new QueryClient({ queryCache: testCache })
619616
const observer = new QueryObserver(testClient, {
620617
queryKey: key,
621618
enabled: false,

src/react/tests/useInfiniteQuery.test.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,7 @@ const fetchItems = async (
4141

4242
describe('useInfiniteQuery', () => {
4343
const queryCache = new QueryCache()
44-
const queryClient = new QueryClient({
45-
queryCache,
46-
defaultOptions: {
47-
queries: {
48-
notifyOnChangePropsExclusions: [],
49-
},
50-
},
51-
})
44+
const queryClient = new QueryClient({ queryCache })
5245

5346
it('should return the correct states for a successful query', async () => {
5447
const key = queryKey()

src/react/tests/useQuery.test.tsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ describe('useQuery', () => {
651651
expect(states[1]).toMatchObject({ data: 'test' })
652652
})
653653

654-
it('should not re-render when notify flags are false and the selected data did not change', async () => {
654+
it('should not re-render when it should only re-render only data change and the selected data did not change', async () => {
655655
const key = queryKey()
656656
const states: UseQueryResult<string>[] = []
657657

@@ -683,6 +683,45 @@ describe('useQuery', () => {
683683
expect(states[1]).toMatchObject({ data: 'test' })
684684
})
685685

686+
it('should re-render when dataUpdatedAt changes but data remains the same', async () => {
687+
const key = queryKey()
688+
const states: UseQueryResult<string>[] = []
689+
690+
function Page() {
691+
const state = useQuery(key, () => 'test', {
692+
notifyOnChangePropsExclusions: [
693+
'data',
694+
'isFetching',
695+
'isLoading',
696+
'isSuccess',
697+
'status',
698+
],
699+
})
700+
701+
states.push(state)
702+
703+
const { refetch } = state
704+
705+
React.useEffect(() => {
706+
setActTimeout(() => {
707+
refetch()
708+
}, 5)
709+
}, [refetch])
710+
711+
return null
712+
}
713+
714+
renderWithClient(queryClient, <Page />)
715+
716+
await sleep(10)
717+
718+
expect(states.length).toBe(3)
719+
expect(states[0]).toMatchObject({ data: undefined, isFetching: true })
720+
expect(states[1]).toMatchObject({ data: 'test', isFetching: false })
721+
expect(states[2]).toMatchObject({ data: 'test', isFetching: false })
722+
expect(states[1].dataUpdatedAt).not.toBe(states[2].dataUpdatedAt)
723+
})
724+
686725
it('should be able to remove a query', async () => {
687726
const key = queryKey()
688727
const states: UseQueryResult<number>[] = []
@@ -1491,7 +1530,7 @@ describe('useQuery', () => {
14911530
expect(fn).toHaveBeenCalledTimes(5)
14921531
})
14931532

1494-
it('should not re-render when a query status changes and notify flags are false', async () => {
1533+
it('should not re-render when it should only re-render on data changes and the data did not change', async () => {
14951534
const key = queryKey()
14961535
const states: UseQueryResult<string>[] = []
14971536

0 commit comments

Comments
 (0)