Harnessing the Power of React Hooks: useState and useEffect
![Harnessing the Power of React Hooks: useState and useEffect](/content/images/size/w1200/2024/06/react-usestate_blog.webp)
React’s useState
and useEffect
hooks are fundamental tools for managing state and side effects in functional components. This post explores interesting and advanced ways to leverage these hooks in your React applications.
Understanding useState
Basic Usage
First, let’s start with the basic usage of useState
.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In this example, we initialize a state variable count
with a value of 0
. The setCount
function updates the state.
Lazy Initialization
Sometimes, initializing state can be an expensive operation. useState
accepts a function for lazy initialization, which is only executed on the initial render.
function expensiveComputation() {
console.log('Computing...');
return 42;
}
function LazyCounter() {
const [count, setCount] = useState(() => expensiveComputation());
return (
<div>
<p>Initial count: {count}</p>
</div>
);
}
In this example, expensiveComputation
is only called once, during the initial render.
Updating State Based on Previous State
When the new state depends on the previous state, it's safer to use a function within setState
to ensure the correct state is applied.
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Click me
</button>
</div>
);
}
![](https://www.gingertechblog.com/content/images/2024/06/image-2.png)
Here, setCount
receives the previous state value (prevCount
) and increments it. The prevCount
is implicitly defined as a parameter of the updater function provided to setCount
.
Why Use prevCount
?
Using prevCount
(or any variable representing the previous state) is important because React's state updates are asynchronous. If you update state based directly on the current state value, you might encounter issues, especially with multiple rapid updates. By using a function form of setState
, you ensure the update is based on the most recent state.
For example:
// Incorrect way (direct state update):
<button onClick={() => setCount(count + 1)}>Click me</button>
// Correct way (function form of state updater):
<button onClick={() => setCount(prevCount => prevCount + 1)}>Click me</button>
The function form ensures that each update is based on the latest state, preventing potential bugs related to stale state values.
Multiple State Variables
Using multiple useState
hooks allows you to manage separate pieces of state, making your code more readable and organized.
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
return (
<form>
<label>
Name:
<input type="text" value={name} onChange={e => setName(e.target.value)} />
</label>
<label>
Email:
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
</label>
</form>
);
}
![](https://www.gingertechblog.com/content/images/2024/06/image-3.png)
State with Objects
When dealing with objects or arrays, ensure you spread the previous state to avoid losing properties.
function UserProfile() {
const [profile, setProfile] = useState({ name: '', age: '' });
const updateName = (name) => {
setProfile(prevProfile => ({
...prevProfile,
name
}));
};
const updateAge = (age) => {
setProfile(prevProfile => ({
...prevProfile,
age
}));
};
return (
<div>
<input
type="text"
value={profile.name}
onChange={e => updateName(e.target.value)}
placeholder="Name"
/>
<input
type="text"
value={profile.age}
onChange={e => updateAge(e.target.value)}
placeholder="Age"
/>
</div>
);
}
![](https://www.gingertechblog.com/content/images/2024/06/image-4.png)
Diving into useEffect
The useEffect
hook lets you perform side effects in function components. It serves the same purpose as componentDidMount
, componentDidUpdate
, and componentWillUnmount
in React class components.
Basic Usage
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <h1> Time Passed: {count} Seconds</h1>;
}
![](https://www.gingertechblog.com/content/images/2024/06/image-5.png)
In this example, a timer increments the count
every second. The cleanup function (clearInterval
) is returned to clear the timer when the component unmounts.
Dependencies
By default, useEffect
runs after every render. You can control when it runs by passing a second argument, an array of dependencies.
function DataFetcher({ query }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`https://api.example.com/data?query=${query}`)
.then(response => response.json())
.then(data => setData(data));
}, [query]);
if (!data) {
return <div>Loading...</div>;
}
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
Here, the effect runs only when query
changes.
Cleanup
Effects often require cleanup to avoid memory leaks. Return a cleanup function from your effect.
function ResizableComponent() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>Window width: {width}</div>;
}
![](https://www.gingertechblog.com/content/images/2024/06/image-6.png)
Combining useState and useEffect
useState
and useEffect
are often used together to manage state and side effects.
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []);
if (!data) {
return <div>Loading...</div>;
}
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
In this example, useEffect
fetches data when the component mounts and updates the data
state.
Conclusion
The useState
and useEffect
hooks are versatile tools in React that offer more than just basic state and effect management.