There is a rule in react about not calling a hook conditionally. AFAIK react only cares about the number of hooks called, so I tried this:
function useMainHook({ isLive }) {
const res = isLive ? useHookA() : useHookB();
return res; // Make sure to return the result
}
If react only cares about how many hooks are called, this should work. Since there is always 1 hook being called.
Is the hook definition above valid?
0
react only cares about the number of hooks called
React doesn’t care about the number of hooks in it’s own sake. React cares about the number of hooks because all hooks (within one component) need to be called in the same order for every render. Thus disallowing conditional hooks.
If we run the below example we see that valueB
is 0, although it should be 10. That is because both valueA
and valueB
are the second useState
within App
and so React can’t distinguish them.
import { useEffect, useState } from "react";
const useHookA = () => {
const [valueA] = useState(0);
console.log("hook a", valueA);
return valueA;
}
const useHookB = () => {
const [valueB] = useState(10);
console.log("hook b", valueB);
return valueB;
}
function App() {
const [hookA, setHookA] = useState(true);
useEffect(() => {
const i = setInterval(() => setHookA(v => !v), 1000);
return () => clearInterval(i);
}, []);
const value = hookA ? useHookA() : useHookB();
return (
<p>Hook {hookA ? "A" : "B"}, value: {value}</p>
);
}
2
function useMainHook({ isLive }) { const res = isLive ? useHookA() : useHookB(); return res; // Make sure to return the result }
If React only cares about how many hooks are called, this should work.
Since there is always 1 hook being called.Is the hook definition above valid?
I’m inclined to say no, this is not a valid usage/definition.
This code may work, but only as a special case and not as a general rule. It depends entirely on what useHookA
and useHookB
actually do, it’s not just what hooks are called right there in the one component/useMainHook
hook. All React hooks from all rendered components in the ReactTree are called each and every render cycle.
Consider the following two counter examples:
const useHookA = () => {
React.useEffect(() => {
console.log("A effect 1");
});
React.useEffect(() => {
console.log("A effect 2");
});
};
const useHookB = () => {
React.useEffect(() => {
console.log("B");
});
};
function App() {
const [state, setState] = React.useState(false);
state ? useHookA() : useHookB();
const toggleState = () => setState((b) => !b);
return (
<div className="App">
<button type="button" onClick={toggleState}>
Toggle
</button>
</div>
);
}
const rootElement = document.getElementById("root");
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.min.js"></script>
<div id="root" />
const useHookA = () => {
const [stateA, setStateA] = React.useState("a");
React.useEffect(() => {
console.log("A");
});
};
const useHookB = () => {
React.useEffect(() => {
console.log("B");
});
};
function App() {
const [state, setState] = React.useState(false);
state ? useHookA() : useHookB();
const toggleState = () => setState((b) => !b);
return (
<div className="App">
<button type="button" onClick={toggleState}>
Toggle
</button>
</div>
);
}
const rootElement = document.getElementById("root");
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.min.js"></script>
<div id="root" />
Your implementation breaks React’s Rules of Hooks.
Don’t call Hooks inside loops, conditions, nested functions, or
try
/catch
/finally
blocks. Instead, always use Hooks at the top
level of your React function, before any early returns. You can only
call Hooks while React is rendering a function component:
- ✅ Call them at the top level in the body of a function component.
- ✅ Call them at the top level in the body of a custom Hook.
It’s not supported to call Hooks (functions starting with
use
) in
any other cases, for example:
- 🔴 Do not call Hooks inside conditions or loops.
- 🔴 Do not call Hooks after a conditional return statement.
- 🔴 Do not call Hooks in event handlers.
- 🔴 Do not call Hooks in class components.
- 🔴 Do not call Hooks inside functions passed to useMemo, useReducer, or
useEffect.- 🔴 Do not call Hooks inside try/catch/finally blocks.
You can re-write your implementation to not break React’s Rules of Hooks.
Example: Unconditionally call all hooks and then conditionally return the expected result.
function useMainHook({ isLive }) {
const resA = useHookA();
const resB = useHookB();
return isLive ? resA : resB;
}