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: replace useEffect with top level ref setting in useStableCallback #2010

Merged
merged 1 commit into from
Nov 17, 2024

Conversation

pavel-krasnov
Copy link
Contributor

Motivation

Using useEffect to update the ref value causes potential issues with calling the callback before the first useEffect invocation. This happens for example in apps created with New Architecture. Setting the callback in the top level of the hook ensures that it is immediately ready to be called.

useEffect(() => {
callbackRef.current = callback;
return () => {
callbackRef.current = undefined;
Copy link
Owner

Choose a reason for hiding this comment

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

we need the effect at least for the clean up

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What exactly do you need to clean the callback up for?

Copy link
Owner

Choose a reason for hiding this comment

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

  callbackRef.current = undefined;

freeing the ref object upon unmounting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand. This is the first time I read anything about a need of cleaning up a ref object in a useEffect cleanup function. What advantages does it have?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, according to ChatGPT:

Without a cleanup function, callbackRef.current is never cleared. This might lead to unexpected issues if there are asynchronous tasks that outlive the component lifecycle, as callbackRef may retain a reference to the function even after the component has unmounted. This can cause memory leaks or even runtime errors if asynchronous code tries to call callbackRef.current after unmounting.

I will modify the pull request, thank you.

@fobos531
Copy link
Contributor

fobos531 commented Nov 13, 2024

@gorhom FYI, I can confirm that this diff fixes the issue of the sheet not appearing with dynamic sizing enabled on new architecture.

This might also fix #2015 & #2020

To recap, this is the version of the callback that works in my case.

import { useCallback, useEffect, useRef } from 'react';

// biome-ignore lint: to be addressed!
type Callback = (...args: any[]) => any;
/**
 * Provide a stable version of useCallback
 * https://gist.github.com/JakeCoxon/c7ebf6e6496f8468226fd36b596e1985
 */
export const useStableCallback = (callback: Callback) => {
  const callbackRef = useRef<Callback>();
  callbackRef.current = callback;

  const memoCallback = useCallback(
    // biome-ignore lint: to be addressed!
    (...args: any) => callbackRef.current && callbackRef.current(...args),
    []
  );
  useEffect(() => {
    return () => {
      callbackRef.current = undefined;
    };
  });
  return memoCallback;
};

@pavel-krasnov
Copy link
Contributor Author

pavel-krasnov commented Nov 14, 2024

@gorhom FYI, I can confirm that this diff fixes the issue of the sheet not appearing with dynamic sizing enabled on new architecture.

This might also fix #2015 & #2020

To recap, this is the version of the callback that works in my case.

import { useCallback, useEffect, useRef } from 'react';

// biome-ignore lint: to be addressed!
type Callback = (...args: any[]) => any;
/**
 * Provide a stable version of useCallback
 * https://gist.github.com/JakeCoxon/c7ebf6e6496f8468226fd36b596e1985
 */
export const useStableCallback = (callback: Callback) => {
  const callbackRef = useRef<Callback>();
  callbackRef.current = callback;

  const memoCallback = useCallback(
    // biome-ignore lint: to be addressed!
    (...args: any) => callbackRef.current && callbackRef.current(...args),
    []
  );
  useEffect(() => {
    return () => {
      callbackRef.current = undefined;
    };
  });
  return memoCallback;
};

This is not totally correct in my opinion, because every time the callback changes, it might be called before the ref is updated.

Do we actually need this useStableCallback hook? I believe that the easiest way to fix those issues is to get rid of it in sake of simplicity.

@gorhom gorhom merged commit e898859 into gorhom:master Nov 17, 2024
@gorhom
Copy link
Owner

gorhom commented Nov 17, 2024

thank you @pavel-krasnov for submitting this PR <3

@pavel-krasnov pavel-krasnov deleted the fix-use-stable-callback branch November 18, 2024 07:16
@ryskin
Copy link

ryskin commented Nov 29, 2024

It seems that this fixed the collapsing of the BottomSheet, but I might be wrong. Why did adding an inset and updating to version 5.06 solve the issue of the BottomSheet closing immediately after opening?

I don’t understand how this could be related. Why would the inset affect the BottomSheet closing? A value of 0 doesn’t work, but any non-zero value seems to fix the collapsing issue, but at 5.05 even inset not helps

@gaearon
Copy link

gaearon commented Dec 2, 2024

I don’t think this is the correct fix. Instead, try supplying the initial value to the ref in the useRef call. For future renders, applying it in an effect is more correct.

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.

5 participants