Search code examples
reactjsreact-hooksuse-effect

Can I use dynamic properties in dependency array for useEffect?


So I have a question regarding useEffect dependenices

This is from the react docs:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

What does this mean exactly, does React keep track of the count variable and its value, and reacts when the value changes, or does React keep track of the first element in the array and its value.

What do I mean by this? Let me explain more. So if we have something like this [name] as dependencies. At the moment of evaluation, the array might result with ['Bob'] or ['Steve']. Clearly this is a change and the useEffect will rerender the component. But how does it check it?

Does it keep track of name or does it keep track of dependencyArray[0]. If we take a look in the previous example, both of these would result to true, name and the first element both changed their values from 'Bob' to 'Steve'. But how does it actually work?

Currently in my code I am using something like this [employees[selectedEmployee].name], where selectedEmployee is something clickable on the UI and it becomes 'Bob' or 'Steve'

ex:

const employees = {
   Bob: {
      name: 'Bob'
   },
   Steve: {
      name: 'Steve'
   }
}

This means that in the end, when evaluated, the dependency array will still result with ['Bob'] --> ['Steve'], and if React is evaluating the dependencyArray[0] then that has clearly changed and component should rerender, but If it keeps track of the reference, then I am changing the reference altogether and it may cause problems.

So what's the correct approach? Can I use dynamic properties like employees[selectedEmployee].name as a dependency?


Solution

  • count is a value, not a reference.

    It's just good old Javascript, nothing fancy:

    const myArray = [ count ]; // new array containing the value of variable 'count'
    const myFunction = () => {
      document.title = `You clicked ${count} times`;
    }
    
    useEffect(
      myFunction,
      myArray
    );
    
    // Means actually:
    // "Run this function if any value in the array
    // is different to what it was last time this useEffect() was called"
    

    does React keep track of the ... value, or ... the reference ?

    React doesn't really 'keep track' of any of them. It only checks the difference to a previous call, and forgets about everything else.

    Can I use dynamic properties as a dependency?

    Yes, you can (because they are not as 'dynamic' as you think).

    So what's the correct approach?

    Better think less of any react-magic going on, but

    • understand that the component is a function, and believe React calls it when necessary and
    • think about the variables (properties and state) used inside it, from a plain Javascript perspective.

    Then your 'dynamic properties' become 'constant variables during one function call'. No matter which variables change dynamically and how, it will always be one value last time and one value now.

    Explaination:

    The important 'trick' here is, that the component is just a javascript function, that is called like 'whenever anything might have changed', and consequently useEffect() is also called (as useEffect() is just a function call inside the component).
    Only the callback function passed to useEffect is not always called.

    useEffect does not render the component, useEffect is called when the component is called, and then just calls the function given to it, or not, depending on if any value in the dependencies array is different to what it was last time useEffect() was called.

    React might rerender the component if in the function given to useEffect there are any changes made to the state or something (anything that makes React to think it has to rerender), but that's as a result of this state change, where ever it came from, not because of the useEffect call.

    Example:

    const MyComponent = (props) => {
    
      // I'm assigning many const here to show we are dealing with local constants.
      // Usually you would use this form (using array destructuring):
      // const [ selectedEmployee, setSelectedEmployee ] = useState( someInitialValue );
    
      const myStateValueAndSetter = useState( 'Bob' );
      const selectedEmployee    = myStateValueAndSetter[0];
      const setSelectedEmployee = myStateValueAndSetter[1];
    
      const employees = {
        Bob: { name: 'Bob' },
        Steve: { name: 'Steve' }
      };
    
      const currentName = employees[ selectedEmployee ].name;
    
      useEffect(() => {
        document.title = 'current name: ' + currentName;
      }, [ currentName ]);
    
      return <MyClickableComponent onClick={( newValue ) => {
        setSelectedEmployee( newValue )
      }}>;
    };
    
    • click on MyClickableComponent calls the current setSelectedEmployee( newValue ) function.
      (The constant selectedEmployee is not changed!)
    • MyComponent() is called again.
      (This is a new function call. All the constants are gone! Only React stores some state in the background.)
    • useState() is called, the result is stored in a new constant selectedEmployee.
    • useEffect() is called, and decides if its callback should be called, depending on the previous and the current value of selectedEmployee.
      If the callback is not called and nothing else is changed, you might not notice that anything has happened at all.
    • <MyClickableComponent ... /> is rendered.