Skip to content
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

fix: Modifying the useFirstMountState code for proper functioning in React 19 strict mode #2622

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

candymask0712
Copy link

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 and example 2, we can see that this issue is new in React 19. Additionally, comparing example 1 and example 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 of isFirst.current immediately becomes false right after the app renders. This is unusual since the initial value of useRef is set to true. In the first rendering, it is correctly true. It appears that React reuses the changed value of isFirst.current (which became false in the first rendering) due to memoization of the result.

example 4) (original code) react 19 + strict mode + with console

Screenshot 2025-02-27 at 12 36 27 AM
// useFirstMountState.ts

export function useFirstMountState(): boolean {
  const isFirst = useRef(true);
  console.log('first', isFirst.current);

  // ...
}

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 in useFirstMountState.

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. Since isFirst.current is updated using useEffect, 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 value true.

스크린샷 2025-02-27 오전 2 01 55

Additional Thoughts

Differentiating the first and second renderings using useEffect seems to better align with the intent of the useFirstMountState hook. In this PR, I also added a cleanup function that resets isFirst.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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as before)

Checklist

  • Read the Contributing Guide
  • Perform a code self-review
  • Comment the code, particularly in hard-to-understand areas
  • Add documentation
  • Add hook's story at Storybook
  • Cover changes with tests
  • Ensure the test suite passes (yarn test)
  • Provide 100% tests coverage
  • Make sure code lints (yarn lint). Fix it with yarn lint:fix in case of failure.
  • Make sure types are fine (yarn lint:types).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

useUpdateEffect is not working as expected in react19
1 participant