React useEffect
What is the useEffect
hook in React?
The useEffect
hook is a React hook that allows you to perform side effects in functional components. Side effects include tasks like data fetching, subscriptions, manually changing the DOM, and timers. The hook runs after the component renders and can optionally clean up resources when the component unmounts or when dependencies change.
How is useEffect
used in a functional component?
useEffect
takes two arguments: a callback function that performs the effect, and an optional dependency array that specifies when the effect should re-run. The effect runs after every render unless you provide the dependency array.
Example of using useEffect
:
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
In this example, the useEffect
hook updates the document title every time the component renders.
What is the dependency array in useEffect
, and how does it work?
The dependency array is an optional second argument to useEffect
. It tells React when to re-run the effect. If the array is empty, the effect runs only once after the initial render (similar to componentDidMount
in class components). If the array contains state or props, the effect will run again whenever any of those dependencies change.
Example of using a dependency array:
useEffect(() => {
// Perform the effect only when `count` changes
document.title = `Count: ${count}`;
}, [count]);
In this example, the effect will only run when the count
state changes. If count
stays the same, the effect will not re-run.
What happens if you omit the dependency array in useEffect
?
If you omit the dependency array, the effect will run after every render, regardless of whether the component's state or props have changed. This can lead to unnecessary re-execution of the effect, which may impact performance.
Example without a dependency array:
useEffect(() => {
console.log('This runs after every render');
});
In this example, the effect will run after every render, which can be undesirable in many cases.
How do you perform cleanup in useEffect
?
If your effect creates side effects that need to be cleaned up (e.g., subscriptions, timers), you can return a cleanup function from the useEffect
callback. The cleanup function will run when the component unmounts or before the effect is re-run if the dependencies change.
Example of cleanup in useEffect
:
useEffect(() => {
const timerID = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timerID); // Cleanup on unmount or before re-run
};
}, []); // Only run on mount
In this example, the interval is cleared when the component unmounts or before the effect runs again (if dependencies were provided). This prevents memory leaks.
What is the difference between useEffect
and useLayoutEffect
?
useEffect
runs asynchronously after the browser has painted the UI, meaning it does not block the UI update. On the other hand, useLayoutEffect
runs synchronously after all DOM mutations but before the browser paints the screen, allowing you to make DOM measurements or updates before the user sees the changes.
For most cases, useEffect
is sufficient. useLayoutEffect
is used in scenarios where you need to measure the DOM or make visual updates that should happen before the screen is updated.
Example of using useLayoutEffect
:
import React, { useRef, useLayoutEffect } from 'react';
function Box() {
const boxRef = useRef(null);
useLayoutEffect(() => {
const box = boxRef.current;
box.style.width = `${box.offsetWidth}px`;
}, []);
return <div ref={boxRef} style={{ height: '100px', backgroundColor: 'lightblue' }}>Resizable Box</div>;
}
In this example, useLayoutEffect
ensures that the box's width is set before the browser paints the screen, preventing any flicker or layout shift.
How can you run an effect only once, like componentDidMount
in class components?
To run an effect only once after the component mounts, you can pass an empty dependency array []
as the second argument to useEffect
. This ensures that the effect runs only after the initial render and does not run again on subsequent renders.
Example of running an effect only once:
useEffect(() => {
console.log('This runs only once, after the component mounts');
}, []); // Empty array ensures the effect runs only once
In this example, the effect will only run once, right after the component is mounted, similar to componentDidMount
in class components.
How do you make API calls with useEffect
?
You can make API calls inside useEffect
and store the results in the component's state. Be sure to handle side effects like setting state or handling errors after the data is fetched. Additionally, clean up any subscriptions or cancel the API request if the component unmounts before the request completes.
Example of making an API call with useEffect
:
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => {
if (isMounted) {
setData(result);
}
})
.catch(err => setError(err));
return () => {
isMounted = false; // Cleanup if the component unmounts
};
}, []); // Empty array to run only once
return (
<div>
{error ? <p>Error: {error.message}</p> : <p>Data: {JSON.stringify(data)}</p>}
</div>
);
}
In this example, the data is fetched from an API and stored in the component's state. The cleanup function ensures that the state is not updated if the component unmounts before the data is received.
What happens when you provide no dependency array to useEffect
?
If you provide no dependency array to useEffect
, the effect will run after every render, both for the initial render and every subsequent update. This behavior can lead to performance issues if the effect performs expensive calculations or makes API calls without checking whether the data has changed.
Example without a dependency array:
useEffect(() => {
console.log('This runs after every render');
});
In this example, the effect runs after every render, which is usually unnecessary and can degrade performance.
How do you run an effect conditionally with useEffect
?
You can run an effect conditionally by providing a dependency array that includes the state or props that should trigger the effect. The effect will only re-run when one of the dependencies changes.
Example of conditionally running an effect:
useEffect(() => {
console.log('Count changed:', count);
}, [count]); // Effect runs only when `count` changes
In this example, the effect runs only when the count
state changes, making it more efficient by avoiding unnecessary executions.
What is the difference between mounting, updating, and unmounting in useEffect
?
In the context of useEffect
:
- Mounting: The effect runs after the component is initially rendered.
- Updating: The effect runs after every subsequent re-render if the dependencies specified in the dependency array have changed.
- Unmounting: If the effect returns a cleanup function, that function is run when the component unmounts or before the effect re-runs due to dependency changes.
Example of handling all three phases:
useEffect(() => {
console.log('Component mounted or updated');
return () => {
console.log('Component unmounted or before update');
};
}, [dependency]);
In this example, the effect runs when the component mounts or when the dependency
changes, and the cleanup function runs before the component unmounts or before the next effect re-runs.