Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.2k
[Fix-5538]: Assign observer's current Result when optimistic read occurs#5573
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
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
125a52a
[Fix-5538]: Assign observer's current Result when an optimistic readi…
incepter 3702e9b
move the condition outside so would make making decision about v4 and…
incepter 4a24dbf
Merge branch 'main' into fix-data-double-ref
TkDodo 622adc9
Flip test on currentResult with placeholderData's order & add test f…
incepter abc4abf
Merge branch 'main' into fix-data-double-ref
TkDodo 2ef9021
Fix tests using the same queryKey
incepter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -240,7 +240,30 @@ export class QueryObserver< | ||
| ): QueryObserverResult<TData, TError> { | ||
| const query = this.client.getQueryCache().build(this.client, options) | ||
| return this.createResult(query, options) | ||
| const result = this.createResult(query, options) | ||
| if (shouldAssignObserverCurrentProperties(this, result, options)) { | ||
| // this assigns the optimistic result to the current Observer | ||
| // because if the query function changes, useQuery will be performing | ||
| // an effect where it would fetch again. | ||
| // When the fetch finishes, we perform a deep data cloning in order | ||
| // to reuse objects references. This deep data clone is performed against | ||
| // the `observer.currentResult.data` property | ||
| // When QueryKey changes, we refresh the query and get new `optimistic` | ||
| // result, while we leave the `observer.currentResult`, so when new data | ||
| // arrives, it finds the old `observer.currentResult` which is related | ||
| // to the old QueryKey. Which means that currentResult and selectData are | ||
| // out of sync already. | ||
| // To solve this, we move the cursor of the currentResult everytime | ||
| // an observer reads an optimistic value. | ||
| // When keeping the previous data, the result doesn't change until new | ||
| // data arrives. | ||
| this.currentResult = result | ||
| this.currentResultOptions = this.options | ||
| this.currentResultState = this.currentQuery.state | ||
| } | ||
| return result | ||
| } | ||
| getCurrentResult(): QueryObserverResult<TData, TError> { | ||
| @@ -764,3 +787,51 @@ function isStale( | ||
| ): boolean { | ||
| return query.isStaleByTime(options.staleTime) | ||
| } | ||
| // this function would decide if we will update the observer's 'current' | ||
| // properties after an optimistic reading via getOptimisticResult | ||
| function shouldAssignObserverCurrentProperties< | ||
| TQueryFnData = unknown, | ||
| TError = unknown, | ||
| TData = TQueryFnData, | ||
| TQueryData = TQueryFnData, | ||
| TQueryKey extends QueryKey = QueryKey, | ||
| >( | ||
| observer: QueryObserver<TQueryFnData, TError, TData, TQueryData, TQueryKey>, | ||
| optimisticResult: QueryObserverResult<TData, TError>, | ||
| options: DefaultedQueryObserverOptions< | ||
| TQueryFnData, | ||
| TError, | ||
| TData, | ||
| TQueryData, | ||
| TQueryKey | ||
| >, | ||
| ) { | ||
| // it is important to keep this condition like this for three reasons: | ||
| // 1. It will get removed in the v5 | ||
| // 2. it reads: don't update the properties if we want to keep the previous | ||
| // data. | ||
| // 3. The opposite condition (!options.keepPreviousData) would fallthrough | ||
| // and will result in a bad decision | ||
incepter marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (options.keepPreviousData) { | ||
| return false | ||
| } | ||
| // this means we want to put some placeholder data when pending and queryKey | ||
| // changed. | ||
| if (options.placeholderData !== undefined) { | ||
| // re-assign properties only if current data is placeholder data | ||
| // which means that data did not arrive yet, so, if there is some cached data | ||
| // we need to "prepare" to receive it | ||
| return optimisticResult.isPlaceholderData | ||
| } | ||
| // if the newly created result isn't what the observer is holding as current, | ||
| // then we'll need to update the properties as well | ||
| if (observer.getCurrentResult() !== optimisticResult) { | ||
| return true | ||
| } | ||
| // basically, just keep previous properties if nothing changed | ||
| return false | ||
| } | ||
5 changes: 1 addition & 4 deletions
5 packages/react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -325,7 +325,7 @@ describe('PersistQueryClientProvider', () => { | ||
| await waitFor(() => rendered.getByText('data: null')) | ||
| await waitFor(() => rendered.getByText('data: hydrated')) | ||
| expect(states).toHaveLength(3) | ||
| expect(states).toHaveLength(2) | ||
| expect(fetched).toBe(false) | ||
| @@ -340,9 +340,6 @@ describe('PersistQueryClientProvider', () => { | ||
| fetchStatus: 'idle', | ||
| data: 'hydrated', | ||
| }) | ||
| // #5443 seems like we get an extra render now ... | ||
| expect(states[1]).toStrictEqual(states[2]) | ||
TkDodo marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }) | ||
| test('should call onSuccess after successful restoring', async () => { | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.