-
Notifications
You must be signed in to change notification settings - Fork 47.6k
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
[React 19] use
is significantly slower in some scenarios than throwing a Promise
#29855
Comments
Added context: this seems to be triggered by So my working theory is that the order of operation
seems to cause this - maybe because |
I'll add to this some additional findings. @phryneas's theory is close, but it seems this only occurs when the original value is updated before the deferred value. Everything stays fast if the deferred value only lags behind one update from the original. In other words, if I log the two values, the fast execution has this timeline: { value: 'a', deferredValue: '' }
{ value: 'a', deferredValue: 'a' }
{ value: 'ab', deferredValue: 'a' }
{ value: 'ab', deferredValue: 'ab' } which I can simulate with But it gets slow with this timeline (i.e. the { value: 'a', deferredValue: '' }
{ value: 'ab', deferredValue: '' }
{ value: 'ab', deferredValue: 'ab' } The promise attached to the empty string is resolved by the time we simulate the I'm still working to see if I can create an isolated reproduction without Apollo or its test suite. Will report back if I have success. |
Update: It seems the issue lies in the way await act(() => user.type('ab')) with await user.type('ab')` I see act warnings, but the test goes back to being fast. Seems there is some combination of things here that cause something to spiral out of control. Here are all the things that work to speed up the test again and exhibit the expected behavior:
If the swap of I'll continue working on a reproduction in a test environment. I was unable to reproduce using a running app in the browser which is a good thing. |
Alright I was able to get a reproduction together that demonstrates the issue in a test. Check out my reproduction here: https://github.com/jerelmiller/react-use-reproduction/blob/master/src/App.test.tsx Run |
Thanks for the detailed investigation, @phryneas and @jerelmiller! The repro you put together (https://github.com/react-native-community/reproducer-react-native) is really helpful in understanding the root cause. It seems the slowness is related to how use interacts with promises generated from multiple user input events within a single act call, specifically when the original value updates before the deferred value. This is interesting behavior, and I'd be glad to help troubleshoot further. Here are some additional thoughts based on your findings: Consider alternative testing approaches: While act is useful for simulating user interactions, could the test be rewritten to avoid the problematic combination of act and multiple user input events within it? Perhaps separate act calls for each user input could be explored. I'm happy to assist in any way possible to move this issue forward. Perhaps we can discuss potential solutions or debugging strategies in a follow-up comment or collaboratively on a pull request if a code change is identified. |
@RacketyWater7 This is a bug report in the React repo. Trying to work around the problem we are reporting here would be counterproductive to the the intent of this issue - identifying the bug in React, reproducting it and getting it fixed. |
I could narrow it down a bit further by replacing This pattern is problematic in combination with await act(async () => {
fireEvent.change(input, { target: { value: "a" } });
await new Promise((resolve) => setTimeout(resolve, 1));
fireEvent.change(input, { target: { value: "ab" } });
}); If you skip the |
@phryneas Can you pack this into a minimal, cloneable repro? Ideally with clear instructions how to repro and what the expected vs actual behavior is for each case. |
@eps1lon we updated the repro that @jerelmiller linked above: https://github.com/jerelmiller/react-use-reproduction/blob/master/src/App.test.tsx For running it, just clone it and run |
Just wanted to note that these |
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! |
bump to keep this open 🙂 |
BREAKING CHANGE: This release has to break some old APIs to stay compatible with React 19. \#\# `render` is now `async` and should be `await`ed This is the core change - due to the impementation of sibling prerendering on the React side, rendering has become more `async` and tests that interacted with `render` in a synchronous fashion would end up with not-resolving suspense boundaries. Please adjust your tests accordingly: ```diff const {takeRender, render} = createRenderStream({ /* ... */ }) - const utils = render(<Counter />) + const utils = await render(<Counter />) ``` \#\# enforcing of `IS_REACT_ACT_ENVIRONMENT == false` In combination with [issues we have have seen in the past](facebook/react#29855), we have deduced that the testing approach of this library only really works in a "real-world" scenario, not in an `act` environment. As a result, we will now throw an error if you try to use it in an environment where `IS_REACT_ACT_ENVIRONMENT` is truthy. We are shipping a new tool, `disableActEnvironment` to prepare your environment for the duration of a test in a safe manner. This helper can either be used with explicit resource management using the `using` keyword: ```ts test('my test', () => { using _disabledAct = disableActEnvironment() // your test code here // as soon as this scope is left, the environment will be cleaned up }) ``` of by manually calling `cleanup`: ```ts test('my test', () => { const {cleanup} = disableActEnvironment() try { // your test code here } finally { cleanup() } }) ``` This function does not only adjust your `IS_REACT_ACT_ENVIRONMENT` value, but it will also temporarily adjust the `@testing-library/dom` configuration in a way so that e.g. calls to `userEvent` will not automatically be wrapped in `act`. Of course you can also use this tool on a per-file basis instead of a per-test basis, but keep in mind that doing so will break any React Testing Library tests that might be in the same file. \#\# `render` is now it's own implementation Previously, we used the `render` function of React Testing Library, but with the new restrictions this is no longer possible and we are shipping our own implementation. As a result, some less-common options are not supported in the new implementation. If you have a need for these, please let us know! * `hydrate` was removed * `legacyRoot` was removed. If you are using React 17, it will automatically switch to `ReactDOM.render`, otherwise we will use `createRoot` > [!CAUTION] > React 17 does not look for `IS_REACT_ACT_ENVIRONMENT` to determine if it is running in an `act`-environment, but rather `typeof jest !== "undefined"`. > If you have to test React 17, we recommend to patch it with a [`patch-package` patch](https://github.com/apollographql/apollo-client/blob/8a4738a8ad7284d247513671628a4ac5917e104c/patches/react-dom-17+17.0.2.patch) \#\# `renderToRenderStream` was removed As you now have to `await` the `render` call, `renderToRenderStream` had no real value anymore. Where previously, the second line of ```js const renderStream = renderToRenderStream(<Component />, combinedOptions) // this was optional in the past const utils = await renderStream.renderResultPromise ``` could be omitted and could save you some code, now that second line would become required. This now was not any shorter than calling `createRenderStream` and `renderStream.render` instead, and as both of these APIs now did the same thing in a different fashion, this would lead to confusion to no more benefit, so the API was removed.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! |
bump to keep this open 🙂 |
Summary
This is something I noticed when I applied the Apollo Client test suite to React 19 - we had one test that was timing out.
I first thought that the test was broken, but it turns out it was just really slow.
Cranking the test that finished in React 18 in under 200 milliseconds to a timeout of 10 seconds for React 19 made it pass (6 seconds would still consistently fail, 7 seconds started to pass on localhost).
Further I noticed that if we use our
__use
polyfill that throws a Promise instead ofuse
, the test finishes in under 500 milliseconds - still slower than React 18, but significantly faster than React 19 with nativeuse
.This is the test in question:
https://github.com/apollographql/apollo-client/blob/8e3edd4f5e5191453ba3ca1a1cc4fc4a02b936e8/src/react/hooks/__tests__/useSuspenseQuery.test.tsx#L9519-L9615
I'd be happy to work with you on further identifying the root cause for this, but I'll be moving houses in a few days, and I just don't have the time to create an isolated reproduction for this within the next 1-2 weeks, I'm really sorry.
Still, I assume this might be important for you, so I'll at least open a ticket to let you know :)
The text was updated successfully, but these errors were encountered: