useState and useEffect
Introduction
In the previous lesson, we learned about the concept of Hooks in React. Now, let’s dive deeper into two foundational hooks: useState and useEffect. These hooks are commonly used for managing component state and handling side effects, making them essential for building dynamic and interactive React applications.
In this lesson, you’ll learn:
- How to manage state in functional components with
useState - How to handle side effects using
useEffect - The syntax and typical use cases for each hook
What is useState?
The useState hook allows you to add and manage state within a functional component. It provides a way to store data that can change over time, such as form inputs, counters, or toggles.
Syntax of useState
const [state, setState] = useState(initialValue);
stateis the current state value.setStateis a function that allows you to update the state.initialValueis the starting value for the state, which can be any data type (number, string, array, object, etc.).
Each time the setState function is called, React re-renders the component with the updated state value.
Example: Using useState for a Counter
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)}>
Increment
</button>
</div>
);
}
In this example:
countis the state variable initialized to0.setCountis the function that updatescounteach time the button is clicked.
Each time setCount is called, React updates the component with the new count value, and the component re-renders to reflect this change.
What is useEffect?
The useEffect hook allows you to perform side effects in functional components. Side effects include actions like fetching data from an API, setting up subscriptions, or updating the DOM.
Syntax of useEffect
useEffect(() => {
// Effect logic goes here
return () => {
// Optional cleanup logic
};
}, [dependencies]);
- The first argument is a callback function containing the side effect.
- The second argument is an optional dependency array. This array determines when the effect should re-run. If it’s empty (
[]), the effect only runs once, likecomponentDidMountin a class component.
Example: Using useEffect to Update the Document Title
import React, { useState, useEffect } from 'react';
function DocumentTitleUpdater() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
In this example:
- The
useEffecthook updates the document title every timecountchanges. - The dependency array
[count]ensures that the effect only runs whencountchanges, preventing unnecessary re-renders.
Using useEffect for Data Fetching
One of the most common use cases for useEffect is to fetch data from an API when a component mounts.
Example: Fetching Data with useEffect
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []); // Empty dependency array means this runs only once on mount
if (loading) return <p>Loading...</p>;
return <div>{JSON.stringify(data)}</div>;
}
In this example:
- The
fetchcall fetches data from an API only once when the component mounts, as indicated by the empty dependency array[]. - Once the data is fetched, the state is updated, triggering a re-render to display the data.
Dependency Array in useEffect
The dependency array is crucial for controlling when useEffect runs. It determines when the effect is re-applied by React.
-
No Dependency Array: The effect runs after every render.
useEffect(() => { // Effect logic here }); -
Empty Dependency Array (
[]): The effect runs only once when the component mounts, similar tocomponentDidMount.useEffect(() => { // Effect logic here }, []); -
Array with Specific Dependencies: The effect runs whenever any dependency in the array changes.
useEffect(() => { // Effect logic here }, [dependency1, dependency2]);
Example with Dependency Array
import React, { useState, useEffect } from 'react';
function EffectWithDependency() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count has changed to ${count}`);
}, [count]); // Runs only when `count` changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
In this example:
- The
useEffecthook only logs to the console whencountchanges, thanks to the dependency array[count].
Effect Cleanup with useEffect
Sometimes, side effects require cleanup to prevent memory leaks, such as removing event listeners or canceling network requests. You can handle this with the cleanup function inside useEffect, which runs when the component unmounts or before the next effect.
Example: Cleaning Up an Interval
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
return () => clearInterval(interval); // Cleanup to clear interval on unmount
}, []);
return <p>Seconds: {seconds}</p>;
}
In this example:
- The
setIntervalfunction increments thesecondsstate every second. - The cleanup function
clearIntervalensures that the interval is cleared when the component unmounts, preventing memory leaks.
Common Pitfalls with useState and useEffect
Here are some common issues to watch out for when working with these hooks:
- Missing Dependencies: Always include all variables that change within the effect in the dependency array to prevent unexpected behavior.
- Infinite Loops: Avoid setting state directly within
useEffectwithout a conditional, as this can cause infinite loops. - Multiple Effects: Split complex logic across multiple
useEffectcalls if necessary, as each hook can handle separate concerns.
Conclusion
In this lesson, we covered:
- How to use the
useStatehook to add and update state in functional components - How to use the
useEffecthook to handle side effects in functional components - The role of the dependency array and how to manage cleanup with
useEffect
The useState and useEffect hooks are fundamental to functional components, enabling you to manage state and side effects effectively. In the next lesson, we’ll explore Custom Hooks, which allow you to encapsulate and reuse component logic across your React applications.
