TL;DR: Ready to unlock the power of functional components in React? Dive into useState and useEffect Hooks! Explore how these tools simplify state management and side effects, empowering you to write cleaner and more maintainable React applications.
React first introduced Hooks in version 16.8, and they have changed the way we write React code ever since. Hooks enable you to extract stateful logic from a functional component, allowing it to be reused and tested independently.
useState and useEffect are two of React’s most useful Hooks. Therefore, a thorough understanding of them is essential for React developers. This blog will assist you in comprehending both of these Hooks.
Before the useState Hook, state management in React could only be done through class components. The useState Hook in functional components serves the same purpose as this.state/this.setState in the class components.
Consider the Counter class in the following example.
class Counter extends React.Component { constructor(props) { this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
In this example, the count is initially set to 0, and on a button click, the count is updated by calling this.setState().
Now, let’s look at doing the same thing with the React useState Hook.
import React, { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
As shown in the example, we must first import the useState Hook from React. Following that, we call the useState Hook from within our Counter function. The React useState Hook returns the current state and the function that updates it, which are count and setCount in our example.
Note: Remember that variables normally disappear when a function exits, but React keeps state variables, so the count variable will be preserved.
Using the useState Hook, you can declare any type of state variable. The Hook can also be used multiple times in a single component.
When we update a state, there can be side effects that occur in parallel with each change. Data fetch requests, direct DOM manipulations, and the use of timer functions such as setTimeout() are examples of side effects. We can use the React useEffect Hook to perform side effects in function components.
Previously, these side effects were achieved using lifecycle methods like componentDidMount(), componentDidUpdate(), and componentWillUnmount(). The useEffect Hook is a combination of all these methods. It accepts a callback function that is invoked whenever a render occurs.
Take a look at the following example to see how we can use lifecycle methods to achieve the side effects.
class Time extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { setInterval( () => this.tick(), 1000 ); } tick() { this.setState({ date: new Date() }); } render() { return ( <div><h2>{this.state.date.toLocaleTimeString()}</h2></div> ); } } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<Time />);
In this example class component, we use the lifecycle methods to set the current time. Now, let’s consider the following example to see how the useEffect Hook can be used to replace the componentDidMount() method.
import React, { useState, useEffect } from "react"; function Time() { const [date, setDate] = useState(new Date()); useEffect(() => { setInterval(() => { setDate(new Date()); }, 1000); }, []); return <div>Time: {date.toLocaleTimeString()}</div>; }
You can see how much easier it is to implement using Hooks rather than lifecycle methods. You can play with the code here.
In React components, there are two types of side effects: those that do not require cleanup and those that do. In the previous example, no cleanup was required. Setting up a subscription, closing a socket, and clearing timers are a few common examples of side effects that require cleanup.
So, let’s see how we can use useEffect with cleanup.
Cleanup in useEffect is important to prevent memory leaks.
If the useEffect() callback returns a function, useEffect() considers this to be an effective cleanup. Take a look at the following example.
import { useEffect } from 'react'; function LogMessage({ message }) { useEffect(() => { const log = setInterval(() => { console.log(message); }, 1000); return () => { clearInterval(log); }; }, [message]); return <div>logging to console "{message}"</div>; }
In the previous example, the cleanup function clears the interval. Rather than being called after the initial rendering, the cleanup function is called before invoking the next side-effect callback. It cleans up the previous side effect and then executes the current side effect.
If no cleanup is performed, there will be multiple console logs when only one updated log is required. Therefore, make sure to use the cleanup function in such scenarios.
By default, the useEffect Hook runs after the first render as well as after each update. However, you might need to skip applying an effect in situations like when certain values haven’t changed since the last re-render.
You can easily skip applying effects by passing an array as a second argument to the useEffect Hook. As arguments, you can pass props or state values. When an argument is passed, the useEffect Hook will be executed after the initial render and only when the argument values change.
Consider the following example, in which the effect is only invoked if the variable count is updated.
useEffect(() => { console.log(count); }, [count]);
If you want to run and clean up the effect only once, you can pass an empty array ([]) as the second argument.
Now, let’s look at some real-world apps for the React useState and useEffect Hooks.
You can fetch API data only when the component mounts by using the React useState and useEffect Hooks. It is not permitted to use async directly for the useEffect function. Instead, you can define and then invoke an async function within the useEffect function.
Refer to the following code example.
import { useEffect, useState } from 'react'; function FetchItems() { const [items, setItems] = useState([]); useEffect(() => { async function fetchItems() { const response = await fetch('/items'); const fetchedItems = await response.json(response); setItems(fetchedItems); } fetchItems(); }, []); return ( <div> {items.map(name => <div>{name}</div>)} </div> ); }
You can also use the React useState and useEffect Hooks to display a loader while fetching data. Here’s the example code for that.
const Statistics = () => { const [stats, setStats] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const getStats = async () => { const stats = await getData(); setStats(stats); setLoading(false); }; getStats(); }, []);
In this article, we discussed how to use two of React’s most essential Hooks: useState and useEffect. If we know the correct usage, they can be used to write cleaner, reusable code. I hope this article helps you understand how these two Hooks work and when should we use them.
Thank you for reading!
The Syncfusion React suite offers over 80 high-performance, lightweight, modular, and responsive UI components in a single package. It’s the only suite you’ll ever need to construct a complete application.
If you have any questions or comments, you can contact us through our support forums, support portal, or feedback portal. As always, we are happy to assist you!