Skip to content
Why hooks cannot be invoked conditionally in ReactJS?

Why hooks cannot be invoked conditionally in ReactJS?

Let's take an example:

import {useState} from "react";
 
const App = () => {
  const [first, setFirst] = useState(1);
  const [second, setSecond] = useState(2);
 
  // do something with the states
};

How does React know which state to return?

The question is how does React know, that the state first and the setter setFirst is associated with useState(1)? This is a valid question because useState only takes in a value, and doesn't get passed with any identifier that associates it with the corresponding state, at least that's what we can conclude by seeing the declaration.

The answer to this question is simple, React stores states and related setters in an Array. We know that arrays are indexed from 0 and the ordering of elements in the array matters when we are trying to fetch a particular element from within it.

Let's visualize this better by looking at useState's implementation:

let componentHooks = [];
let hookIndex = 0;
 
const useState = value => {
  let stateAndSetter = componentHooks[hookIndex];
  if (stateAndSetter) {
    // Not the first time we are seeing this hook.
    // Thus return its details and move to next hook.
    hookIndex++;
    return stateAndSetter;
  }
 
  const setState = newValue => {
    stateAndSetter[0] = newValue;
    reRenderDOM();
  };
 
  // first time initialization
  stateAndSetter = [value, setState];
 
  componentHooks[hookIndex] = stateAndSetter;
  hookIndex++;
  return stateAndSetter;
};

The above code is the simplified version of useState. As you can see, we just add the state and setter pair to an Array, and fetch the array elements based on the index of the hook in the order of invocation. So for useState(1) the hookIndex is 0 and for useState(2) the hookIndex is 1.

Rules of writing hooks in ReactJS

There are just two rules that we need to keep in mind:

  1. All hooks ought to be invoked at the top level of the component.
  2. Hooks should NOT be invoked conditionally.

Why hooks ought to follow certain rules?

As we saw in the first section, if we change the order of the hooks, then the hook index internally will also change. How's that a problem though? Take the following example:

let isFirstRender = true;
 
const App = () => {
  if (isFirstRender) {
    const [firstState, setFirstState] = useState(1);
    isFirstRender = false;
  }
 
  const [secondState, setSecondState] = useState(2);
  const [thirdState, setThirdState] = useState(3);
 
  return (
    // triggers second render when clicked
    <button onClick={() => setSecondState(10)}>Click Here</button>
  )
};
  • After first render the componentHooks array would look like so:
componentHooks = [
  [1, setFirstState], // index 0
  [2, setSecondState], // index 1
  [3, setThirdState], // index 2
]
  • But on second render, the data becomes inconsistent, like so:
componentHooks = [
  [10, setSecondState], // index 0 is bad data
  [2, setSecondState], // index 1
  [3, setThirdState], // index 2
]

Our expectation was that the index 1 in componentHooks would be updated, but rather index 0 got updated in the second render, triggered by the button click. This happened because of the conditional hook invocation.

In the second render, the conditional statement didn't get executed and thus hookIndex didn't change, as it stayed at index 0 which pointed to [1, setFirstState].

After the second render, we overwrote the 0th index losing reference to the firstState and corresponding setter, which would create a lot of bugs in our App. And this is why we ought to follow the rules specified by the React team while invoking hooks!

References