The React renderer is complaining because your component’s logic is trying to use more hooks (like useState, useEffect, useContext, etc.) during a render than it did during the previous render. This breaks the fundamental rule of hooks: they must be called in the same order on every render.

Here’s why this happens and how to fix it:

1. Conditional Hook Calls Inside Loops, Conditions, or Nested Functions

This is the most common culprit. You’re calling a hook inside an if statement, a for loop, or a function that isn’t a direct child of your component’s top level. React needs to know exactly which hooks are called, and in what order, every single time the component renders.

Diagnosis: Carefully examine your component’s render function. Look for any use... calls that are not directly at the top level of your component, or that are wrapped in if, else, for, while, or other conditional logic.

Fix: Move all hook calls to the top level of your component. If you need conditional logic, render different components based on the condition, or use the hook’s return value conditionally, but don’t call the hook itself conditionally.

// BAD: Hook inside an if statement
function MyComponent({ showInput }) {
  if (showInput) {
    const [value, setValue] = useState(''); // This will cause the error
    return <input value={value} onChange={(e) => setValue(e.target.value)} />;
  }
  return <div>No input shown</div>;
}

// GOOD: Conditional rendering of a component that uses hooks
function InputComponent() {
  const [value, setValue] = useState('');
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

function MyComponent({ showInput }) {
  if (showInput) {
    return <InputComponent />;
  }
  return <div>No input shown</div>;
}

Why it works: By moving the hook call outside the conditional, or by conditionally rendering a separate component that contains the hook, you ensure that the hook is always called in the same place in the component’s execution flow.

2. Hooks Inside Nested Functions (Not Event Handlers)

Similar to the above, but specifically when a hook is called within a function that is defined inside your component but not directly invoked as an event handler during the render itself.

Diagnosis: Search for use... calls within any function defined inside your component’s body, like a helper function or a callback that isn’t an event handler.

Fix: Move the hook call to the top level of the component. If the hook’s logic is only needed within that nested function, consider if the nested function itself should be a separate component.

// BAD: Hook inside a non-event-handler function
function MyComponent() {
  const [data, setData] = useState(null);

  function fetchData() {
    const [loading, setLoading] = useState(false); // This will cause the error
    if (!data) {
      setLoading(true);
      // ... fetch data ...
      setLoading(false);
    }
  }

  useEffect(() => {
    fetchData();
  }, [data]);

  return <div>{data ? data.message : 'Loading...'}</div>;
}

// GOOD: Move hook to top level, use logic conditionally
function MyComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false); // Hook is at top level

  function fetchData() {
    if (!data && !loading) { // Check loading state here
      setLoading(true);
      // ... fetch data ...
      setData(fetchedData);
      setLoading(false);
    }
  }

  useEffect(() => {
    fetchData();
  }, [data, loading]); // Add loading to dependency array if needed

  return <div>{data ? data.message : 'Loading...'}</div>;
}

Why it works: React tracks hooks based on their declaration order. A hook declared inside a function that might or might not be called during render creates an inconsistent order. Moving it to the top ensures it’s always declared.

3. Incorrectly Using useRef for State Updates

While useRef can persist values across renders, it doesn’t trigger re-renders when its .current property changes. If you’re trying to manage UI state with useRef and then conditionally render based on that state, you might be hitting this. More commonly, if you’re trying to conditionally initialize a ref that gets used later, it could cause issues.

Diagnosis: Look for useRef calls that might be inside conditional logic, especially if you’re then trying to derive UI state from the ref’s .current value without a corresponding useState.

Fix: Use useState for values that should trigger a re-render and affect the UI. useRef is for mutable values that don’t need to cause re-renders (like DOM elements, timers, or previous state values).

// BAD: Trying to conditionally initialize a ref that influences render
function MyComponent({ initialValue }) {
  const myValueRef = useRef(); // This might be initialized conditionally later

  if (initialValue !== undefined && myValueRef.current === undefined) {
    myValueRef.current = initialValue; // This assignment doesn't cause a re-render
  }

  // If component re-renders for other reasons, and initialValue is still set,
  // the ref might already have a value, but if it was undefined, it might get set again.
  // This can lead to inconsistent hook counts if not careful.

  return <div>{myValueRef.current}</div>; // This won't update if myValueRef.current changes without a re-render
}

// GOOD: Use useState for values that affect rendering
function MyComponent({ initialValue }) {
  const [myValue, setMyValue] = useState(initialValue); // Initialize with useState

  // If initialValue changes and you want to update state, use useEffect
  useEffect(() => {
    setMyValue(initialValue);
  }, [initialValue]);

  return <div>{myValue}</div>;
}

Why it works: useState is designed to manage state that impacts rendering. React knows to re-render when setMyValue is called. useRef’s .current property is just a mutable container, and changes to it are not observed by React for rendering purposes.

4. Custom Hooks with Conditional Logic

If you’ve created your own custom hooks (functions starting with use), the same rules apply. You cannot call hooks conditionally within your custom hook.

Diagnosis: Inspect your custom hook implementations. Look for any use... calls inside if statements, loops, or nested functions within the custom hook itself.

Fix: Refactor your custom hook to ensure all hooks are called at the top level, or conditionally render/return values derived from hooks.

// BAD: Custom hook with conditional hook call
function useConditionalData(shouldFetch) {
  if (shouldFetch) {
    const [data, setData] = useState(null); // Error here
    // ... fetch logic ...
    return data;
  }
  return null;
}

// GOOD: Custom hook that conditionally returns data
function useConditionalData(shouldFetch) {
  const [data, setData] = useState(null); // Hook is always called

  useEffect(() => {
    if (shouldFetch) {
      // ... fetch logic ...
      // setData(fetchedData);
    }
  }, [shouldFetch]); // Effect depends on the condition

  return data; // Return the state, which might be null if not fetching
}

Why it works: Just like built-in hooks, custom hooks are subject to the same rules. The custom hook itself is called on every render, and React expects the sequence of hook calls within that custom hook to be identical on every render.

5. Early Returns Based on Props/State Before Hooks

If you have an early return statement in your component that bypasses hook calls, and then a subsequent render does call those hooks, you’ll see this error.

Diagnosis: Examine your component for return statements that appear before all hooks are declared.

Fix: Ensure that all hook calls are placed at the top of your component function, before any conditional logic or early returns that might skip them. If a condition should prevent rendering, it should ideally be handled by conditionally rendering the entire component or a parent component.

// BAD: Early return skips hooks
function MyComponent({ user }) {
  if (!user) {
    return <div>Please log in</div>; // This skips the useState call
  }

  const [profile, setProfile] = useState(null); // This hook might be skipped

  // ... rest of component using profile ...

  return <div>...</div>;
}

// GOOD: Conditional rendering of the component or its content
function MyComponent({ user }) {
  const [profile, setProfile] = useState(null); // Hook is always called

  useEffect(() => {
    if (user) {
      // fetch profile
    }
  }, [user]);

  if (!user) {
    return <div>Please log in</div>; // Still a valid early return if no hooks are skipped
  }

  // ... rest of component using profile ...

  return <div>...</div>;
}

Why it works: The useState hook (or any hook) needs to be called on every render. If an early return prevents it from being called, React loses track of the hook’s state and order, leading to the error on the next render when it is called.

6. React Strict Mode and Development Builds

Sometimes, this error only appears in development mode due to React’s Strict Mode, which intentionally double-invokes components to help find issues with state updates and effects. If your component behaves differently when Strict Mode is enabled, it’s a strong indicator that your hook usage is not idempotent or is relying on an inconsistent render order.

Diagnosis: Check your index.js or App.js for <React.StrictMode>. Temporarily remove it to see if the error disappears. If it does, the problem is likely one of the above, but exacerbated by Strict Mode’s checks.

Fix: Apply the fixes mentioned above. Strict Mode is a valuable tool for catching these kinds of bugs early.

Why it works: Strict Mode helps uncover bugs by making them more apparent. If your component works fine without Strict Mode but breaks with it, it means there’s a subtle flaw in how your component handles state or effects across multiple, potentially consecutive, renders.

The next error you’ll likely encounter is a "Too many re-renders" error if you fix the hook order but introduce an infinite loop of state updates.

Want structured learning?

Take the full React course →