Skip to content

fix: useHandler cleanup on fast refresh#9075

Open
akwasniewski wants to merge 1 commit intomainfrom
@akwasniewski/fix-fastRefresh-useHandler-cleanup
Open

fix: useHandler cleanup on fast refresh#9075
akwasniewski wants to merge 1 commit intomainfrom
@akwasniewski/fix-fastRefresh-useHandler-cleanup

Conversation

@akwasniewski
Copy link
Copy Markdown

@akwasniewski akwasniewski commented Mar 10, 2026

Summary

The doDependenciesDiffer flag is set to false after the first hot reload (it is true after all other reloads). This PR adds a check whether we have a hot reload, and set doDependenciesDiffer to true in that case.

Moreover, I noticed that the cleanup in the useEffect doesn't check whether the ref it cleans is the one it started with. Due to some race conditions after a hot reload the cleanup runs just after the mount, cleaning the newly attached ref. I added a check in cleanup.

The motivation for the fix is that RNGH relies on the doDependenciesDiffer flag to rebuild its handlers. See PR with a workaround in RNGH: software-mansion/react-native-gesture-handler#3997

Test plan

Tested on the following example:

Details
import React from 'react';
import { View, Text } from 'react-native';
import { useHandler } from 'react-native-reanimated';

export default function TestComponent() {

  const handlers = {
    onEvent: () => {
      'worklet';
      console.log("run!");
    }
  };

  const { doDependenciesDiffer } = useHandler(handlers, []);

  // const testString = "comment this out";

  return (
    <View style={{ marginTop: 100, alignItems: 'center' }}>
      <Text style={{ fontSize: 20, marginVertical: 30, fontWeight: 'bold' }}>
        doDependenciesDiffer: <Text style={{ color: doDependenciesDiffer ? 'green' : 'red' }}>{String(doDependenciesDiffer)}</Text>
      </Text>
    </View>
  );
}

Before the fix, on first reload the doDependenciesDiffer flag was set to false after first hot reload, this caused issues in RNGH (see software-mansion/react-native-gesture-handler#3997).
After the fix it is true on every hot reload.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug where useHandler's doDependenciesDiffer flag was incorrectly set to false after the first hot reload (fast refresh), causing downstream consumers like React Native Gesture Handler (RNGH) to not rebuild their handlers. It introduces a fast refresh detection mechanism and fixes a race condition in the useEffect cleanup.

Changes:

  • Added a useHasFastRefreshed utility hook that detects fast refresh by comparing useMemo (reset by React on refresh) with useRef (preserved on refresh)
  • Modified useHandler to reset its internal state and force doDependenciesDiffer = true when a fast refresh is detected
  • Fixed a race condition where the useEffect cleanup could null out a freshly re-initialized initRef by guarding the cleanup with a reference identity check

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/react-native-reanimated/src/hook/utils.ts Added useHasFastRefreshed hook and its useMemo/useRef imports
packages/react-native-reanimated/src/hook/useHandler.ts Integrated fast refresh detection, reset initRef on refresh, guarded cleanup against race conditions, and included isFastRefresh in doDependenciesDiffer computation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/react-native-reanimated/src/hook/utils.ts Outdated
Comment thread packages/react-native-reanimated/src/hook/useHandler.ts Outdated
@akwasniewski akwasniewski requested a review from tjzel March 10, 2026 11:31
@tjzel
Copy link
Copy Markdown
Collaborator

tjzel commented Mar 11, 2026

I'm blocking this PR due to #9081

@MatiPl01 MatiPl01 force-pushed the @akwasniewski/fix-fastRefresh-useHandler-cleanup branch from 8486b93 to e7de456 Compare April 15, 2026 18:16
@MatiPl01 MatiPl01 requested a review from Copilot April 15, 2026 18:16
Copy link
Copy Markdown
Member

@MatiPl01 MatiPl01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some changes. It should be now aligned with the new useHandler implementation.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +18 to +24
const signal = useMemo(() => ({}), []);
const prev = useRef(signal);
if (prev.current === signal) {
return false;
}
prev.current = signal;
return true;
Comment on lines +151 to +158
// On fast refresh we want the hook to behave as a first render rather
// than a re-render, so `doDependenciesDiffer` is reported as true and
// consumers can rebuild native bindings tied to pre-refresh JS identities.
// See https://github.com/software-mansion/react-native-reanimated/pull/9075.
const isFastRefresh = useIsFastRefresh();
if (isFastRefresh) {
stateRef.current = null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants