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);
state
is the current state value.setState
is a function that allows you to update the state.initialValue
is 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:
count
is the state variable initialized to0
.setCount
is the function that updatescount
each 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, likecomponentDidMount
in 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
useEffect
hook updates the document title every timecount
changes. - The dependency array
[count]
ensures that the effect only runs whencount
changes, 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
fetch
call 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
useEffect
hook only logs to the console whencount
changes, 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
setInterval
function increments theseconds
state every second. - The cleanup function
clearInterval
ensures 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
useEffect
without a conditional, as this can cause infinite loops. - Multiple Effects: Split complex logic across multiple
useEffect
calls if necessary, as each hook can handle separate concerns.
Conclusion
In this lesson, we covered:
- How to use the
useState
hook to add and update state in functional components - How to use the
useEffect
hook 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.