React Hooks have transformed the way developers build functional components, offering a more streamlined and efficient approach to managing state and side effects. However, to harness the full power of Hooks and ensure the stability of your applications, it's crucial to adhere to two fundamental rules: Only Call Hooks at the Top Level and Only Call Hooks from React Functions. In this blog post, we'll delve into these rules and demonstrate how to fetch data using the useQuery
hook within a functional component.
Rule 1: Only Call Hooks at the Top Level
The rule that hooks should be called in the same order each time a component renders is crucial for how React keeps track of the state and other values associated with each hook. Let's delve a bit deeper into why this consistency is so important.
How React Tracks Hooks
When you use hooks in a functional component, React keeps track of them in the order they are called. Each hook call is associated with a particular position in the sequence. For example, if you have a component with two useState
hooks followed by a useEffect
hook, React associates the first state with the first position, the second state with the second position, and the effect with the third position.
Why Order Matters
React relies on the order of hook calls to match between renders to associate the internal state with the corresponding hook correctly. This is because React does not have a way to identify hooks other than their order in the component function.
If the order of hooks changes between renders, React will not be able to match the internal state correctly with the hooks. This could lead to unpredictable behaviour and bugs. For example, if a useState
hook is sometimes called and sometimes not (based on a condition), React might associate the state with the wrong hook on subsequent renders, leading to incorrect values being used in your component.
Example
Consider the following component:
const MyComponent = ({ condition }) => {
const [count, setCount] = useState(0);
if (condition) {
const [name, setName] = useState('React');
// React associates 'name' state with the second position
}
// More hooks or logic...
return <div>{count}</div>;
};
If condition
changes between renders, the order in which the hooks are called would change, causing React to lose track of which state corresponds to which hook.
To maintain consistency and ensure that React can correctly manage the internal state associated with each hook, it is essential to always call hooks in the same order on every render. This is why placing hooks at the top level of your component and not inside loops, conditions, or nested functions is crucial. By adhering to this rule, you help React maintain the correct association between hooks and their state, leading to more predictable and bug-free components.
Example of Violating the Rule:
const MyComponent = () => {
const [count, setCount] = useState(0);
if (count > 0) {
useEffect(() => {
// This is a violation of the rule.
console.log('Count is greater than 0');
}, [count]);
}
return <div>{count}</div>;
};
Correct Usage:
const MyComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
if (count > 0) {
console.log('Count is greater than 0');
}
}, [count]);
return <div>{count}</div>;
};
Rule 2: Only Call Hooks from React Functions
The rule that hooks should only be called from within the body of a function component or a custom hook is crucial for maintaining the integrity and predictability of React's functional programming model. Let's explore this rule further to understand its significance:
React-Specific Context
Hooks are designed to tap into React's state and lifecycle features, which are only available within the context of React components or custom hooks. When you use hooks inside function components or custom hooks, React ensures that the hooks are tied to the correct component instance and have access to the relevant React-specific context, such as state, props, and lifecycle events.
Why Only in React Functions?
Consistency and Order: Hooks rely on the order in which they are called to associate the internal state with the correct hook. Calling hooks only from React functions ensures that the hooks are called in a consistent order every time the component renders. This consistency is crucial for React to correctly map the internal state to each hook.
Component Lifecycle: Hooks are designed to tap into React's component lifecycle (e.g., mounting, updating, unmounting). When called from a React function component, hooks can properly integrate with this lifecycle, allowing React to manage their state and effects in sync with the component's lifecycle events.
Isolation: By restricting hooks to React functions, each hook's state and effects are isolated to the specific component or custom hook they are called from. This isolation ensures that the state and effects are scoped correctly and do not leak or interfere with other components.
Example of Incorrect Usage
function outsideFunction() {
const [count, setCount] = useState(0); // Incorrect: useState called outside a component
// ...
}
class MyComponent = () => {
handleClick = () => {
const [isOpen, setIsOpen] = useState(false); // Incorrect: useState called in a class component
// ...
};
// ...
}
Correct Usage
const MyFunctionalComponent = () => {
const [count, setCount] = useState(0); // Correct: useState called inside a function component
// ...
const handleClick = () => {
// Event handler defined inside a component
// ...
};
// ...
};
const useMyCustomHook = () => {
const [state, setState] = useState(null); // Correct: useState called inside a custom hook
// ...
};
Conclusion
Adhering to the two rules of hooks—Only Call Hooks at the Top Level and Only Call Hooks from React Functions—is essential for ensuring the reliability and predictability of your React applications. By following these rules and using hooks appropriately, you can leverage the full power of React Hooks to build efficient and maintainable functional components.