React Hook "useEffect" is called conditionally
Your code, after an if
statement that contains return
, is equivalent to an else
branch:
if(!firebase.getCurrentUsername()) {
...
return null
} else {
useEffect(...)
...
}
Which means that it's executed conditionally (only when the return
is NOT executed).
To fix:
useEffect(() => {
if(firebase.getCurrentUsername()) {
firebase.getCurrentUserQuote().then(setQuote)
}
}, [firebase.getCurrentUsername(), firebase.getCurrentUserQuote()])
if(!firebase.getCurrentUsername()) {
...
return null
}
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. You can follow the documentation here.
I couldn't find the use case in the above code. If you need the effect to run when the return value of firebase.getCurrentUsername()
changes, you might want to use it outside the if
condition like:
useEffect(() => {
firebase.getCurrentUserQuote().then(setQuote)
}, [firebase.getCurrentUsername()]);
I had a similar problem with the same error message, where the order of variable declarations was the source of the error:
Bad example
if (loading) return <>loading...</>;
if (error) return <>Error! {error.message}</>;
const [reload, setReload] = useState(false);
Good example
const [reload, setReload] = useState(false);
if (loading) return <>loading...</>;
if (error) return <>Error! {error.message}</>;
The hook needs to be created before potential conditional return blocks
The issue here is that when we are returning null
from the if block
, the useEffect
hook code will be unreachable, since we returned before it, and hence the error that it is being called conditionally.
You might want to define all the hooks first and then start writing the logic for rendering, be it null or empty string, or a valid JSX.
I would argue there is a way to call hooks conditionally. You just have to export some members from that hook. Copy-paste this snippet in codesandbox:
import React from "react";
import ReactDOM from "react-dom";
function useFetch() {
return {
todos: () =>
fetch("https://jsonplaceholder.typicode.com/todos/1").then(response =>
response.json()
)
};
}
const App = () => {
const fetch = useFetch(); // get a reference to the hook
if ("called conditionally") {
fetch.todos().then(({title}) =>
console.log("it works: ", title)); // it works: delectus aut autem
}
return null;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here's an example with a wrapped useEffect
:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
function useWrappedEffect() {
const [runEffect, setRunEffect] = React.useState(false);
useEffect(() => {
if (runEffect) {
console.log("running");
setRunEffect(false);
}
}, [runEffect]);
return {
run: () => {
setRunEffect(true);
}
};
}
const App = () => {
const myEffect = useWrappedEffect(); // get a reference to the hook
const [run, setRun] = React.useState(false);
if (run) {
myEffect.run();
setRun(false);
}
return (
<button
onClick={() => {
setRun(true);
}}
>
Run
</button>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);