Hooks is just a JavaScript method, but it should be used in the correct way. We provide a linter plug-in to automatically check.
Hooks is called only at the top level
Do not call Hooks in loops, conditional statements, or nested methods. To be called at the top level of your React method. Following this method, you can ensure that hooks are called in the same order every time the component is rendered. This enables React to correctly save state data in the case of multiple usestates and useeffects.
Hooks is only called in the function component
Hooks is not called in ordinary JavaScript methods. You can only
-
Call Hooks in the function component
-
Call Hooks in custom Hooks
Following this can ensure that the stateful logic in all components is clear enough.
ESLint plug-in
There is an ESLint plug-in called ESLint plugin react hooks to enforce these two rules. If you want to try, you can add this plug-in to your project:
npm install eslint-plugin-react-hooks // Your ESLint configuration { "plugins": [ // ... "react-hooks" ], "rules": { // ... "react-hooks/rules-of-hooks": "error", // Check Hook rules "react-hooks/exhaustive-deps": "warn" // Check the dependency of effect } }
The official plans to introduce this plug-in into the Create React App and other tools in the future.
reason
As we learned earlier, we can use multiple state or effect hooks in a single component.
function Form() { // 1. Use the name state variable const [name, setName] = useState('Mary'); // 2. Use an effect for persisting the form useEffect(function persistForm() { localStorage.setItem('formData', name); }); // 3. Use the surname state variable const [surname, setSurname] = useState('Poppins'); // 4. Use an effect for updating the title useEffect(function updateTitle() { document.title = name + ' ' + surname; }); // ... }
How does React know which state corresponds to useState? The answer is that React depends on the order in which Hooks are called. Since Hooks is called in the same order every time, our example is no problem.
// ------------ // First render // ------------ useState('Mary') // 1. Use 'Mary' to initialize the state variable named name useEffect(persistForm) // 2. Add effect to save the form operation useState('Poppins') // 3. Use 'Poppins' to initialize the state with the variable name surname useEffect(updateTitle) // 4. Add effect to update the title // ------------- // Secondary rendering // ------------- useState('Mary') // 1. Read the state of the variable name name (the parameter is ignored) useEffect(persistForm) // 2. Replace the effect of the saved form useState('Poppins') // 3. Read the state of the variable named surname (the parameter is ignored) useEffect(updateTitle) // 4. Replace the effect of the updated title // ...
As long as the call order of the Hook is consistent between multiple renderings, React can correctly associate the internal state with the corresponding Hook. But what happens if we put a Hook (e.g. persistForm effect) call into a conditional statement?
// 🔴 Using Hook in a conditional statement violates the first rule if (name !== '') { useEffect(function persistForm() { localStorage.setItem('formData', name); }); }
In the first rendering, name! = = "" The condition value is true, so we will execute the Hook. But the next time we render, we may empty the form and the expression value becomes false. At this time, the rendering will skip the Hook, and the call order of the Hook has changed:
useState('Mary') // 1. Read the state of the variable name name (the parameter is ignored) // useEffect(persistForm) // 🔴 This Hook is ignored! useState('Poppins') // 🔴 2 (previously 3). Failed to read state with variable name surname useEffect(updateTitle) // 🔴 3 (previously 4). Replacing the effect of the updated Title failed
React doesn't know what the Hook of the second useState should return. React will think that the call of the second Hook in this component is the same as the last rendering, and corresponds to the effect of persistForm, but this is not the case. From here on, subsequent Hook calls are executed in advance, resulting in bug s.
This is why Hook needs to be called at the top level of our component. If we want to execute an effect conditionally, we can put the judgment inside the Hook:
useEffect(function persistForm() { // 👍 Place conditional judgment in effect if (name !== '') { localStorage.setItem('formData', name); } });
Note that if you use the linter plug-in, you don't need to worry about this problem. But you need to know why Hooks works like this and what problems this rule avoids.