React hooks are like a toolkit you use when assembling furniture. Imagine having a set of tools that let you customize and organize your furniture pieces to fit your needs. Each tool has a specific use, making it easier to handle and change individual components. In this topic, we look closely at the primary hooks, figure out what they do and the rules they abide by, and uncover the potency of this game-changing method in modern React development. Let's utilize the power of hooks to enhance your React applications.
Comparing class components and functional components with hooks
In traditional class components, managing state and lifecycle methods was essential. The state held dynamic data, and lifecycle methods controlled the behavior of the component throughout its lifecycle. The this keyword let you reach the component's properties and methods. Conversely, functional components do not employ the this keyword. For instance, to update the state, you would use the this.setState() method, and the initial state would be set in the class's constructor.
Functional components let you handle the state with a useState hook. Hooks like useEffect provide the functionality of lifecycle methods. In class components, you would use methods like componentDidMount, componentDidUpdate, and componentWillUnmount.
Managing class components without hooks is like crafting a tool from scratch; it entails more effort and attention to detail. In contrast, using hooks in functional components lets you reapply stateful logic without changing the structure of your components. This eases the sharing of state logic among various components in your project. Hooks provide a streamlined toolkit for handling state and lifecycle in functional components, offering a simple and effective way to build dynamic and interactive React applications.
Basic hooks
React provides built-in hooks to manage state, side effects of functional components, and more. These hooks simplify complex tasks, making functional components more versatile so developers can build interactive and responsive UIs with clear, readable code. Let's look at the purpose of the basic ones and their core syntax.
useState allows components to declare and manage state, which helps with dynamic rendering and user interactions. While declaring it, you set the initial value as an argument in the useState function. Then, to manage state, you use the setState function, as the state cannot be modified directly.
const [state, setState] = useState(initialState);useEffect manages different parts of a component's lifecycle. It runs after every render and is commonly used to fetch data, subscribe to external events, or clean up resources when the component unmounts. We call these operations side effects, or just effects.
useRef provides a way to create mutable object properties that remain consistent throughout renders without causing any re-renders. It's often utilized to directly access and interact with the DOM. Its basic usage includes creating a ref object and attaching it to a React element. useRef is valuable for storing and accessing values throughout renders without causing unnecessary ones. This hook is typically used for interacting with the DOM, like getting a reference to a DOM element.
useReducer manages more complex state logic in a component. This is an alternative to useState when state transitions are complex and include multiple sub-states, or when the next state depends on the previous one. This hook is helpful when state transitions are detailed and need a more organized approach than useState.
useMemo lets you save the result of a calculation between re-renders. We call this operation memoization. It's handy when you have an expensive computation that relies on certain inputs, and you want to avoid unnecessary repetition. This optimization can make things run faster by stopping superfluous calculations.
useCallback is utilized to prevent a callback function from being regenerated during every render. This can be good in scenarios where you pass callbacks to child components to stop unnecessary re-renders of child components when the parent renders again.
Custom hooks
Custom hooks in React are hooks you define to wrap up reusable logic. They always start with "use" (e.g. useCustomHook) and can utilize other hooks or create entirely new functions. Custom hooks let you extract and share logic that manages state across different components.
You create custom hooks as functions.
They can leverage existing hooks to handle complex logic.
Like standard hooks, custom hooks can accept arguments and return values.
They improve the way you organize and reuse code by splitting up different tasks.
Rules of hooks
Developers must follow two rules to ensure the proper and consistent functioning of React hooks within functional components. Abiding by these rules helps maintain the integrity of component state and lifecycle management.
Only call hooks at the top level
Hooks should be called at the top of the functional component, and not within loops, conditions, or nested functions. Consider this component, where the useEffect hook is utilized inside a condition.
import React, { useState, useEffect } from 'react';
const InvalidComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// This usage is invalid because useEffect is called conditionally
if (count > 5) {
useEffect(() => {
console.log('Effect when count is greater than 5');
}, [count]);
}
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default InvalidComponent; In this example, useEffect is called conditionally within another useEffect, breaking the rule. This could lead to unpredictable behavior because the order of hook calls might change depending on the condition. To adhere to the rule, you should call hooks at the top of your functional component, outside the scope of any conditions or loops.
import React, { useState, useEffect } from 'react';
const ValidComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// This effect runs whenever the 'count' state changes
console.log(`Effect on count change. Current count: ${count}`);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default ValidComponent;In this example, the useEffect hook is called at the top of the component, adhering to the rule as it is not nested within conditions or separate functions. The first effect is triggered only upon mount, and the second effect is activated when the count state changes.
By invoking hooks solely at the top, you make sure that hooks are called in the same order every time a component is rendered. This is very important for the proper functioning of hooks and keeping their state across different renders.
Don't call hooks from regular JavaScript functions
Hooks should only be called from React functional components or custom hooks, not standard JavaScript functions. Here's an example of a custom hook, useFetchData, that utilizes useState and useEffect hooks and returns the data along with the loading state from fetching an API.
import { useState, useEffect } from 'react';
function useFetchData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading };
}
export default useFetchData;You can use hooks in your custom hooks, thereby making them reusable across different components, but they cannot be invoked in a .js file. This practice ensures that all stateful logic in a component is clear from the code and guarantees that the React app works correctly.
To wrap up:
only call hooks at the top level of a component;
call hooks from React functional components;
call hooks from your custom hooks.
Neglecting these rules might result in issues or unexpected behavior in React apps.
You can read more on this subject in Mastering the useState Hook in React on the Hyperskill Blog.
Conclusion
You have learned that React hooks significantly modify how you handle stateful logic in functional components, providing a simpler and more direct approach compared to class components. Basic hooks like useState and useEffect are the core for managing state and side effects. Custom hooks let you extract stateful logic to reuse across different components. The rules for hooks ensure their consistency and organization, which contributes to the reliability of your code.
When comparing class to functional components, hooks eliminate the need for classes, thus simplifying component structures. If you start using this new approach, you will find the process of React development more efficient and enjoyable. As you familiarize yourself with React hooks, try them out in various situations and see how much they improve your React applications.