A Guide on the useEffect React Hook

Bailey Baker
Beam Benefits

--

While writing React code you will come across numerous React hooks. One of the most common React hooks is useEffect. The useEffect hook is used to perform side effects. Effects let you step outside of how React works and synchronize your components with some other external system. Here is an example:

import { useEffect, useState } from "react";function EmployeeList() {
const [employees, setEmployees] = useState([]);
useEffect(() => {
async function fetchEmployees() {
const response = await fetch("/employees");
const fetchedEmployees = await response.json();
setEmployees(fetchedEmployees);
}
fetchEmployees();
}, []);
return (
<div>
{employees.map((name) => (
<div>{name}</div>
))}
</div>
);
}

Some examples of side effects might be retrieving data, DOM manipulation, and subscribing to an external service. After the initial render of the component containing the useEffect, the code inside the useEffect runs. You can pass a dependency array as the second argument that contains variables the effect depends on. Any change in these variables will re-render the component. If no dependency array is given, the effect will run on every render. This is important to remember because the useEffect might end up being called unnecessarily and can lead to performance issues. It is also important to use useEffects sparingly to make your code less error-prone, easier to follow, and run faster.

There is also the option to run a cleanup function inside the effect that will be cleaned up when the component is unmounted. This can be helpful for cleaning up things like event listeners, timers. etc. that are no longer needed. Not using a cleanup function for things that are no longer needed can lead to memory leaks and other problems. Here is a basic example of the cleanup function:

const TimerComponent = () => {
const [isPlaying, setIsPlaying] = useState(false);
const timer = useRef(null);

const startTimer = () => {
timer.current = setInterval(() => {
console.log("tick");
}, 1000);
};

useEffect(() => {
if (isPlaying) {
startTimer();
}

return () => {
// stop the running timer when this component unmounts
clearInterval(timer.current);
};
}, [isPlaying]);

return <Button onClick={() => setIsPlaying(true)}>Start playing</Button>
};

When it comes to testing code with a useEffect, it isn’t much different than any other test using React Testing library. Just like usual, it’s important to think about how the user makes the code run and make the test do that. Here is an example of a test with a useEffect using the above example:

// Using the above example
// Assuming 'fetch' is mocked to return an array of the names
// ['Harrison', 'Bill', 'Dua']
import { render, screen } from '@testing-library/react'describe('EmployeeList', () => {
const setupComponent = () => render(<EmployeeList />)
it('displays employee names on the screen', () => {
setupComponent()
expect(screen.getByText('Harrison')).toBeInTheDocument()
expect(screen.getByText('Bill')).toBeInTheDocument()
expect(screen.getByText('Dua')).toBeInTheDocument()
})
})

Another thing to remember with useEffects is that they are different than events and components. Components always have the same lifecycle. The component lifecycle is as follows: a component mounts when added to the screen, updates when it receives new props/state, then it unmounts and is removed from the screen. Effects are independent from the component’s lifecycle. They synchronize some external system to the state/props. Effects are also different from events. Event handlers only re-run when you perform the same interaction again. However, effects rerun if a value like props/state is different from the last render.

This is just a quick overview of useEffect. It is important to keep in mind when is a good time to use this hook and others. Looking through the React documentation (https://react.dev/) is also a helpful place to look for more information on how hooks work and when to use them.

--

--