Reputation: 6692
From the react official documentation we know that "React relies on the order in which Hooks are called". So is there anything wrong with "reserving" a spot for a hook if I want to call it conditionally?
function Component({flag, depA, depB}) {
if (flag) {
// just "reserving a spot"
useEffect(() => {}, [null, null])
} else {
useEffect(() => {
// ... actual hook
}, [depA, depB])
}
return <></>
}
If this works, would it also work for useCallback
? useLayoutEffect
? useMemo
? useImperativeHandle
?
I've tested all of this and in much more complicated contexts, it seems to work even though the linter complains. Am I missing something?
PS: if it looks kind of useless just like this, it's because the end goal is to have the main part of the hook be lazy loaded with import()
, and before the import is triggered and resolved, just reserve the spots for hooks.
Upvotes: 52
Views: 106423
Reputation: 1
Similar to Hamid Shoja's answer, but with reusability in mind. Gives you full freedom.
export default function RunHook(props: HookWrapperProps) {
props.hook();
return null;
}
export type HookWrapperProps = {
hook: () => void;
}
// usage example:
function SomeComponent({ state } : { state: 'inactive' | 'pending' | 'active' }) {
return <>
{state !== 'pending' && <RunHook hook={useSomething}/>}
{state === 'active' && <RunHook hook={useSomethingElse}/>}
</>;
}
Upvotes: 0
Reputation: 1
function useValues(isPdf: boolean) {
const useGetValues = isPdf ? usePdfValues : useRegularValues;
return useGetValues();
}
Upvotes: -2
Reputation: 99525
I was just thinking about this today. I believe that while it 'breaks the rules', there's nothing that React could do to tell the difference between the two.
So while it breaks the rules, if you have a good enough reason, understand the risks, then the 'rules' is just dogma.
React basically knows which useEffect hook is which, by counting invocations. Calling useEffect
conditionally is bad, specifically because the amount of times useEffect
gets called cannot change.
Your example is conditional, but React can't detect it because in either condition you call it once.
However, the example you mention seems like it doesn't need this. There's good reasons to do things the 'normal' way, because as you can see from other commenters here, it causes confusion and surprise, and we don't like surprise =)
If you are lazily loading in some functionality, just have your useEffect
hook call the function when it is ready.
Upvotes: 28
Reputation: 4768
My solution is like this, and it works for all hooks, however it's better to use it when you have to since it can cause confusion and surprise
const ConditionalEffect1 = () => {
useEffect(() => {}, [])
return null
}
const ConditionalEffect2 = ({depA, depB}) => {
useEffect(() => {}, [depA, depB])
return null
}
const Component = ({ flag, depA, depB }) => {
return flag
? <ConditionalEffect1 />
: <ConditionalEffect2 depA={depA} depB={depB} />
}
Upvotes: 27
Reputation: 2552
You cannot call useEffect conditionally as that is breaking the rules of hooks instead you could do the following:
useEffect(() => {
if (flag) {
console.log("do something");
} else {
console.log("do something else");
}
}, [depA, depB]);
Upvotes: 16