Before we can build our own custom hooks, we should review the rules of hooks. These rules apply to React’s built-in hooks, like useState()
and useEffect()
, as well as any custom hooks that we create. Let’s review.
Rule #1: Only call hooks from React function components. We cannot use hooks in class components since hooks aren’t supported. We also cannot use hooks in regular JavaScript functions. This is solely for the purpose of code maintainability. By following this rule, we can easily separate our hook-based logic from the rest of our application’s logic.
Rule #2: Only call Hooks at the top level of your function components. Do not call them within other functions, conditionals, or loop blocks. This one has to do with making sure that our hooks are called every time, and in the same order, each time a component re-renders.
As users interact with the application and the application re-renders, React calls all of the functions that are defined inside our components on each new render, including our hooks. So, how can React keep track of the useState()
or useEffect()
calls that are made between renders?
React keeps track of the data and callback functions that are managed with hooks based on their order in the component’s definition. If we run our hooks only during some re-renders and not others, this order will get jumbled causing unexpected results.
For example, if we were to put a useEffect()
call inside an if
statement:
const [searchQuery, setSearchQuery] = useState(''); if (!searchQuery) { useEffect(() => { fetchData(`someapi.com/search?q=${searchQuery}`); }, [searchQuery]); }
… then the component would call useState()
every time but would only sometimes call useEffect()
. If we were to use this hook in our application, we might run into the following error:
Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
Instead, we can accomplish the same goal while consistently calling our hook every time:
const [searchQuery, setSearchQuery] = useState(''); useEffect(() => { if (!searchQuery) { fetchData(`someapi.com/search?q=${searchQuery}`); } }, [searchQuery]);
By following this rule, we can ensure that our hooks are called in the same order and on every render.
Note: Be careful not to confuse executing a hook on every render with executing the callback passed to it on every render.
useEffect()
callbacks may not be called on every render depending on the dependency array values. However, theuseEffect()
hook itself must be called on every render.
Instructions
We’ve again updated the counter such that the background will change to a fun gradient if the counter reaches 10. Try it out and you’ll see that we are getting that pesky error!
In Counter.js
fix the hook to make sure that we are not breaking any rules.