-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Adjust relative timing of useFragment
and useQuery
#11083
Conversation
🦋 Changeset detectedLatest commit: d5db4c4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
/release:pr |
A new release has been made for this PR. You can install it with |
size-limit report 📦
|
await waitFor(() => { | ||
if (IS_REACT_18) { | ||
expect(count).toBe(3) | ||
} else { | ||
expect(count).toBe(12) | ||
if (testFailures.length > 0) { | ||
throw testFailures[0]; | ||
} | ||
expect(count).toBe(12) | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of tests had "just 3 renders in React 18" because during the tests, some expect
statement failed.
That would be swallowed by React and not propagate to jest - but interrupt the React renders for good. The test would just stop going, but not really test anything.
I've changed a lot of tests to this pattern of atestFailures
array to collect errors during rendering, and then checking that inside the waitFor
.
Checking it after the waitFor
would be more optimal, but if anything interrupted the counting from happening, the test would just time out without ever reporting why. This way the test times out, but then errors with the first in-component error or failed expect
.
There is a ton more of those tests with a render behaviour that seems to have gotten problematic with React 18 (and we will have to fix those!) - but so far I only fixed those tests that were somehow affected by the timing changes.
useFragment
and useQuery
@@ -68,7 +68,7 @@ export function useLazyQuery<TData = any, TVariables extends OperationVariables | |||
if (!execOptionsRef.current) { | |||
execOptionsRef.current = Object.create(null); | |||
// Only the first time populating execOptionsRef.current matters here. | |||
internalState.forceUpdate(); | |||
internalState.forceUpdateState(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the one situation where we want to always call the useState
setter update - at this point in time, the useSyncExternalStore
result has not changed from the last render, and thus React would skip the rerender, which in some situations is necessary.
/release:pr |
A new release has been made for this PR. You can install it with |
Just to add some context here, to better understand what's going on:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After talking through the details of this PR with Lenz, this seems like a solid approach to addressing the useFragment
timing issue with useQuery
given some larger constraints 🙌 🎉
One notable fact I learned in the course of our discussion: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified. Thanks for sharing your very thorough deep dive here, @phryneas!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stuff all looks great! Thanks for digging into this so far. I've not nothing else that @alessbell hasn't already mentioned. Thanks!
@@ -11,8 +11,6 @@ import { mockSingleLink } from '../../../../testing'; | |||
import { graphql } from '../../graphql'; | |||
import { ChildProps } from '../../types'; | |||
|
|||
const IS_REACT_18 = React.version.startsWith('18') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 this PR is worth it for this change alone 🙌
Co-authored-by: Jerel Miller <jerelmiller@gmail.com>
✅ Deploy Preview for apollo-client-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Ensures that
useQuery
triggers rerenders faster or at the same time asuseFragment
.Before this change,
useQuery
used auseState
setter callback to trigger arerender instead of the
useSyncExternalState
forceRender
callback.Due to a bug in React (facebook/react#25191), these renders
are not batched together.
That could lead to situations where a child accessing a fragment that was evicted
from the cache would rerender (without data) before a parent
useQuery
componenthad a chance to unmount the child.
After this change,
useQuery
anduseFragment
still don't rerender exactly at the same momentin time - but it is guaranteed that
useQuery
will always rerender beforeuseFragment
will,which should fix most problems.
I know, it's late for this, but this is the last moment in time we have to fix
useFragment
before it gets stable - and we should not release it and then immediately change it's timing.Checklist: