You might have come across this warning in your modern React adventure. It is fairly easy to spot issues in functional components that return too early meaning that some hook cannot be always executed.

But there are more nuanced cases where general guideline is hard to apply because the problem is not obvious at first sight. In the following example I'll use Redux hooks. Let's take a look at this functional component:

import * as React from "react";
import { useDispatch, useSelector } from "react-redux";
import { actions as domain1Actions, selectors as domain1Selectors } from "Models/domain1";
import { selectors as domain2Selectors } from "Models/domain2";

export function Header() {
  const dispatch = useDispatch();
  const prepare = () => dispatch(domain1Actions.prepare());
  const header = useSelector(domain1Selectors.header);
  const isBusy = useSelector(domain1Selectors.isBusy) || useSelector(domain2Selectors.isBusy);

  React.useEffect(() => {
      prepare();
  }, []);

  return (
      <h2>
          {isBusy ? "loading..." : header}
      </h2>
  );
}

Header.js

This component runs a side effect once on first render to prepare something in domain1. In the beginning isBusy is false because neither domain1 nor domain2 selector returns true. And the component renders perfectly fine in these circumstances.

Problem occurs when domain1Selectors.isBusy returns true:

React has detected a change in the order of hooks React has detected a change in the order of hooks

React points out that the problem lies in useEffect:

Cannot read property length of undefined Cannot read property length of undefined

which doesn't really directly map to what could be expected from the implementation. But the root cause lies in the very isBusy definition. The fix is to split definitions and in result useSelector hook invocations:

import * as React from "react";
import { useDispatch, useSelector } from "react-redux";
import { actions as domain1Actions, selectors as domain1Selectors } from "Models/domain1";
import { selectors as domain2Selectors } from "Models/domain2";

export function Header() {
    const dispatch = useDispatch();
    const prepare = () => dispatch(domain1Actions.prepare());
    const header = useSelector(domain1Selectors.header);
    const isBusy1 = useSelector(domain1Selectors.isBusy);
    const isBusy2 = useSelector(domain2Selectors.isBusy);

    React.useEffect(() => {
        prepare();
    }, []);

    return (
        <h2>
            {isBusy1 || isBusy2 ? "loading..." : header}
        </h2>
    );
}

Header.js Fix

This way each hook is run every time and React has no problem diff'ing dependencies for the side effect, which are determined by looking at hooks that were run before the effect.