Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I rely on the useEffect order in a component?

Consider following pattern:

const Comp = ({ handler })=> {
  // handler is some callback of the form ( ) => void
  useSomeHook(handler);
  //...
}

function useSomeHook(cb) {
  const ref = useRef()

  useEffect(() => {
    ref.current = cb; // update ref before second useEffect
  })

  useEffect(() => {
    // use the current (most recent) ref value
    const iv = setInterval(() => { ref.current() }, 3000) 
    return () => { clearInterval(iv) };
  }, []) // run only after first render
}

Question: Can I rely on the fact, that first useEffect is always executed before second useEffect, so ref has already been updated?

Background: My intent is to avoid memoizing passed-in callback in Comp (useCallback) and setting cb as dep. I am specifically interested, if this mutable ref pattern is valid in React world - above is just a contrived example.

I thought, the answer would be a clear "Yes!", after having read a tweet from Dan Abramov. Though, following issue states:

We do not make guarantees about sibling order, whether within a component or between siblings. This is because strong guarantees there hinder refactoring. Within a component, you should be able to re-order Hooks—especially, custom ones.

Did I have interpreted above statement wrong - or are the links a bit contradictory? Appreciate any clarifications.

PS: I am aware of linked post. This question is more about the general pattern, official docs and related to mentioned contradictory(?) statements.

Thanks!

like image 303
bela53 Avatar asked Apr 09 '20 13:04

bela53


People also ask

Does the order of useEffect matter?

If we have multiple instances of useEffect in the component, all the useEffect functions will be executed in the same order as they are defined inside the component.

Do useEffect hooks run in order?

Documentation mentions that by using this hook, we tell react that your component needs to do something after the it is done rendering. IMPORTANT: After the component is done rendering, it checks each useEffect hook sequentially in order which they are written.

Which useEffect runs first parent or child?

Child Effects Fire First So the useEffect Hook behaves similarly to the class lifecycle methods. One behaviour to note is that the child callback is fired prior to the parent callback.

Does useEffect always run on first render?

By default, useEffect will run on initial render as well as every future render (update) of your component.

Can you use useeffect in a class component?

You can’t use useEffect (or any other hook) in a class component. Hooks are only available in functional components. If you want to refactor your lifecycle methods to use useEffect, you have to refactor entire class components writ large. This is both time-consuming and prone to error. What if you could refactor just this one part of the code?

How to use useeffect (and other hooks) in class components?

How to Use useEffect (and other hooks) in Class Components 1 Using useState. 2 Thinking in Effects. However, working with useEffect is a bit different. It’s designed to replace several lifecycle... 3 Use Hooks With Adapter Components. We can see a solution: create a functional component that encapsulates the useEffect... More ...

Does the order you call the useeffect hooks matter?

The order you call them in matters, as far as I'm aware, two useEffect calls in a component will always run in the same order. That's how React identifies which hook is which (based on order).

How to use useeffect () in react functional component?

Using useEffect () in React.js functional component. The React hook useEffect helps in adding componentDidUpdate and componentDidMount combined lifecycle in React’s functional component. So far we know we can add lifecycle methods in stateful component only. To use it, we will need to import it from react −.


Video Answer


1 Answers

The order you call them in matters, as far as I'm aware, two useEffect calls in a component will always run in the same order. That's how React identifies which hook is which (based on order).

One of the main things you can't do is conditionally run a hook for the reason mentioned above: React keeps track of hooks that are called based on the order (index) they are called in the component.

This quote, is talking about the order of cleanup calls in components and between parent and child components. Typically, what you do in the clean up part shouldn't be something that would affect other hooks. Usually what you do is clean up any lasting side effect in your hook. Things like cancelling API calls and clearing intervals.

We guarantee that parent effects are destroyed before the child ones. The reason for this is that parents often tend to depend on some resource created by the child. Such as removing a listener from a DOM node managed imperatively by a child. If the child disposes its resources first, the parent might not be able to properly clean itself up. This is not specific to Hooks — it’s how componentWillUnmount works as well.

We do not make guarantees about sibling order, whether within a component or between siblings. This is because strong guarantees there hinder refactoring. Within a component, you should be able to re-order Hooks—especially, custom ones. Between siblings, it is expected that you can re-order them safely too. It is also common that only one sibling updates or unmounts, so dependencies between siblings cannot be reliable anyway.

The order between parent and child are set. For example, a Parent component that renders 2 Children.

<Parent>
  <Child/>
  <Child/>
</Parent>

Edit: This is how I'd refactor your code:

I'd make sure to include the cb in the initial useRef, and then use the cb in the dependency array of the first effect to update the ref.

Then you'd make sure to clear the interval in the second effect.

const Comp = ({ handler }) => {
  // handler is some callback of the form ( ) => void
  useSomeHook(handler);
  //...
};

function useSomeHook(cb) {
  const ref = useRef(cb);

  useEffect(() => {
    ref.current = cb; // update ref before second useEffect
  },[cb]);

  useEffect(() => {
    const id = setInterval(() => {
      ref.current();
    }, 3000); // use the current (most recent) ref value
    return () => {
      clearInterval(id);
    };
  }, []); // run only after first render
}
like image 92
Zachary Haber Avatar answered Oct 22 '22 23:10

Zachary Haber