react-navigation / hooks

React hooks for convenient react-navigation use

Home Page:https://reactnavigation.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Proposal: useNavigationEffect() hook

benseitz opened this issue · comments

In addition to useNavigationEvents(handler), I propose two useNavigationEffect(handler)hooks, that look and feel similar to the official react useEffect(handler):

useNavigationEffectInsideTransition(() => {
    console.log('screen component did focus');
    return function cleanup() {
      console.log('screen component will blur');
    };
  });

and

useNavigationEffectOutsideTransition(() => {
    console.log('screen component will focus');
    return function cleanup() {
      console.log('screen component did blur');
    };
  });

As with the useEffect(handler) hook, the return of a cleanup function is optional.

To be honest the main problem is naming the two hooks to clarify if the effect is called inside the transitions or outside. Any alternative naming proposals are appreciated.
In my opinion these hooks would be much more intuitive, since people will be familiar with the useEffect(handler) hook.

Are there any common use cases that require the combination of
willFocus & willBlur or didFocus & didBlur
instead of
willFocus & didBlur or didFocus & willBlur?

What are your thoughts on this?

@sophiebits tweet today reminded me of this topic I opened a few months ago:

Fun fact: the original proposed design for Hooks didn't have useEffect; it instead had useLifeCycles(didMount, didUpdate, willUnmount);
We came up with useEffect and the "dependencies" array argument later. Glad we did.
- Sophie Alpert

I still believe that useNavigationEvents(handler) would be a nicer API. Does someone else feel the same?

Hi,

I understand what you want and somehow agree that you generally want to handle events inside or outside the transition, but not often a mix of the 2 (at least I don't remember having the need for that so far, unless i listen to all 4 events at once).

You can also want to handle a single event, not both inside/outside, and with only didBlur code, this would be a bit weird (but I understand you propose an addition, not a replacement).

What you propose seems more like a shortcut than a primitive, and it can probably be implemented in userland. I'm not against it, but I think it will be hard to name it correctly and we can think about implementing this later if we find usage of useNavigationEvents too verbose and users are complaining.

I'm not totally fan of the current api anyway, as it forces us to receive all events melted inside the same callback and switch/case on the event type.

I'd rather advocate for such solutions

  useNavigationEvents({
    willFocus: evt => {},
    didFocus: evt => {},
    willBlur: evt => {},
    didBlur: evt => {},
  });

Or eventually one single event:

  useNavigationEvent("willFocus",evt => {
    
  });

I also like the useEffect hook, but for me its value is more related to the args parameter that you can pass so that it triggers or not the effect, which somehow reduce the boilerplate needed for many usecases usually implemented with lifecycle methods. useNavigationEvents does not take such params currently (but it should use it internally)

We have a useFocusEffect hook in React Navigation 5 which I think is similar to what's proposed here: https://reactnavigation.org/docs/en/next/use-focus-effect.html

The hooks mentioned for event listeners are quite different imo, and solve a different use case. The use cases for useFocusEffect are more about managing subscriptions (such as adding a listener for back button, connecting to socket only when the screen is active etc.) rather than doing a one-off task on an event.

While this use case can be achieved with events (similar to how same thing as useEffect can be done with lifecycle events), they aren't the right primitive for this use case. To achieve similar behaviour of running something on focus, then cleaning up on blur/unmount, the user will need to keep a reference to to the subscription, make sure the effect re-runs when a dependency changes etc, which is cumbersome.

Regarding transitions, I think it's not necessary to add separate hook for this because if the effect is expensive, you can run them in InteractionManager.runAfterInteractions (which is also cancellable with the cleanup function).

useFocusEffect is being implemented here: #43 and it's going to be quite similar to the same hook on v5, to ensure smooth library upgrade.

In my app I'm using something like this also to ensure not showing some expensive comps until transition completes, does this makes sense to you ?

export const useIsFocusedLazy = () => {
  const isFocused = useIsFocused();
  const [isFocusedLazy, setFocusedLazy] = useState(isFocused);
  useEffect(() => {
    let cancelled = false;
    if (isFocused) {
      InteractionManager.runAfterInteractions(() => {
        if (!cancelled) {
          setFocusedLazy(true);
        }
      });
    } else {
      InteractionManager.runAfterInteractions(() => {
        if (!cancelled) {
          setFocusedLazy(false);
        }
      });
    }
    return () => {
      cancelled = true;
    };
  }, [isFocused]);

  return isFocusedLazy;
};

Not sure if this kind of stuff should be included, or if this could be an userland recipe.

You can cancel the InteractionManager directly without having a cancelled property.

export const useIsFocusedLazy = () => {
  const isFocused = useIsFocused();
  const [isFocusedLazy, setFocusedLazy] = useState(isFocused);

  useEffect(() => {
    const delayedCallback = InteractionManager.runAfterInteractions(() => {
      setFocusedLazy(isFocused);
    });
      
    return delayedCallback.cancel;
  }, [isFocused]);

  return isFocusedLazy;
};

IMO no need to keep it in the core.

Ah thanks, totally missed that, last time I read the types I thought it was returning a promise and didn't see the cancel()

image