Why is this an issue?

React relies on the order in which Hooks are called to correctly preserve the state of Hooks between multiple useState and useEffect calls. This means React Hooks should be called in the same order each time a component renders and should not be called inside loops, conditions, or nested functions.

Additionally, this rule ensures that the Hooks are called only from React function components or custom Hooks.

function Profile() {
  const [ordersCount, setOrdersCount] = useState(0);
  if (ordersCount !== 0) {
    useEffect(function() { // Noncompliant: Hook is called conditionally
      localStorage.setItem('ordersData', ordersCount);
    });
  }

  return <div>{ getName() }</div>
}

function getName() {
  const [name] = useState('John'); // Noncompliant: Hook is called from a JavaScript function, not a React component
  return name;
}

Instead, always use Hooks at the top of your React function, before any early returns.

function Profile() {
  const [ordersCount, setOrdersCount] = useState(0);
  useEffect(function() {
    if (ordersCount !== 0) {
      localStorage.setItem('ordersData', ordersCount);
    }
  });

  const [name] = useState('John');
  return <div>{ name }</div>
}

Exceptions

No issue will be raised when a function using React Hooks is explicitly typed as FC, React.FC, FunctionComponent, or React.FunctionComponent, even if its name starts with an underscore. The underlying rules-of-hooks implementation identifies React function components by PascalCase naming only, so underscore-prefixed functions would otherwise be incorrectly flagged despite being valid React components.

import { useState, useEffect, type FC } from 'react';

const _Message: FC<{ name: string }> = (props) => {  // Compliant by exception: explicitly typed as FC
  const [value, setValue] = useState(0);
  useEffect(() => { console.log(value); }, [value]);
  return <div>{props.name}</div>;
};

const _Panel: React.FC<{ title: string }> = ({ title }) => {  // Compliant by exception: explicitly typed as React.FC
  const [open, setOpen] = useState(false);
  return <div>{title}</div>;
};

Resources

Documentation