fix: Modifying the useFirstMountState code for proper functioning in React 19 strict mode #2622
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.
to fix #2611
Hello! I am delighted to submit a PR to react-use, which I frequently use. 😁 While browsing the issues, I became curious about one particular matter, so I investigated and wrote some code.
Description
Issue
useUpdateEffect executes the callback function twice on mount in React 19's strict mode #2611
Example Code for Reproduction
useUpdateEffect is not working properly in React 19 strict mode.
By comparing
example 1
andexample 2
, we can see that this issue is new in React 19. Additionally, comparingexample 1
andexample 3
shows that the issue occurs when strict mode is enabled. (The issue arises if the alert—being the callback function of useUpdateEffect—is executed during the initial rendering.)example 1) react 18 + strict mode
example 2) react 19 + strict mode
example 3) react 19 + no strict mode
Investigation of the Issue's Cause
To investigate the cause of this issue, I checked the React 19 patch notes. Among the various changes, #25583 PR is suspected to be the cause. React's #25583 includes changes that allow the memoized result from the first pass to be reused.
In
example 4
, I added console logs throughout the code to continuously monitor state changes. The most notable difference is that during the second execution triggered by strict mode, the value ofisFirst.current
immediately becomesfalse
right after the app renders. This is unusual since the initial value ofuseRef
is set totrue
. In the first rendering, it is correctly true. It appears that React reuses the changed value ofisFirst.current
(which becamefalse
in the first rendering) due to memoization of the result.example 4) (original code) react 19 + strict mode + with console
As can be seen from the screenshot, the callback console.warn in useUpdateEffect is executed twice even during the initial execution. This behavior deviates from the intended functionality, so I improved the situation by using
useEffect
inuseFirstMountState
.example 5) (modified code) react 19 + strict mode + with console
In
example 5
, I used the same code that I am submitting in this PR. SinceisFirst.current
is updated usinguseEffect
, it is safe from the behavior of memoized result reuse in strict mode. The code inside useEffect executes after the second rendering, so in both the first and second renderings, it starts with the valuetrue
.Additional Thoughts
Differentiating the first and second renderings using
useEffect
seems to better align with the intent of theuseFirstMountState
hook. In this PR, I also added a cleanup function that resetsisFirst.current
back to true. Although this code is not intended to solve the current issue (removing it does not change the behavior), I thought it would be a more desirable behavior considering that React has decided to use memoized values in strict mode.Initially, I wanted to add tests to confirm the changed behavior in React 19, but I couldn’t find an appropriate method. Since the behavior in strict mode is quite peculiar, reproducing it would require additional test code. If you have any good ideas, please let me know! I will incorporate your suggestions and add the corresponding code.
Thank you for reading this lengthy PR. 😁
Type of change
Checklist
yarn test
)yarn lint
). Fix it withyarn lint:fix
in case of failure.yarn lint:types
).