Getting to Know Hooks in React: useEffect()

R. Cory Stine
4 min readApr 1, 2022

Last week, I started my exploration of React Hooks with the most prominent option of the bunch: useState(). This hook allows us to initialize and set state within a functional component — an essential tool in React development. But there are other hooks that are equally as useful. Today, I’m going to try to wrap my head around a function that can be used in lieu of traditional lifecycle methods: useEffect().

This hook is a bit more abstract. It’s fairly easy to understand what “State” is in useState(), but what exactly is “Effect” in useEffect()?

To truly grasp the answer to this question, we need to back up and talk about Pure Functions. A Pure function will always return the same output, as long as the same input is passed in as an argument. They are simple and elegant, making the results of our function predictable.

const multiply = (x,y) => x * y;multiply(2,4); 
//=> 8

This is a very simple example of a Pure Function. No matter how many times I insert the arguments 2 and 4 into the multiply function, it will always return 8. It’s reliable and flexible. That’s why, whenever possible, we prefer using Pure functions in JavaScript.

Side Note: This is a huge topic and perhaps one I will address in a future blog, but I only need the basics for our purposes here.

React components are almost always Pure functions. Think about it: when you pass in specific props as an argument, the result will remain the same every time you use the same values.

export default function App() {
return <Book title="American Gods" />
}
const Book = (props) => {
return <p>{props.title}</p>; //=>American Gods
}

When we pass in the title “American Gods” as props, it will always be the value returned when it is called in the Book function. If we were to change the title to “Neverwhere”, the value returned will always be “Neverwhere”.

The “Effect” in useEffect() is reference to a “side effect” — an action that takes place outside of our React components. Sometimes, it’s essential that we use side effects:

  • When we make a request for data from a backend API.
  • When we want to directly update the DOM.
  • When we use timing functions like setInterval() or setTimeout().

This could be a problem, because by introducing side effects into our code, we are also introducing the type of unpredictability we are trying to avoid in a Pure function.

That’s what makes useEffect() such a significant tool in our arsenal: it allows us to perform side effects but without impacting the component’s ability to render or perform.

Let’s give it a try. As always, the first thing we need to do is import useEffect() into our component.

import { useEffect } from 'react;

Once we’ve created our component, we want to make sure to call useEffect before we render any JSX. We also want to pass in two arguments:

  • A callback function that will be called after the component renders.
  • A dependencies array, which will include all of the values our side effect requires.
import { useEffect } from 'react;const Book = ({ title }) => {
useEffect(() => {
document.title = title;
}, [title]);

return <h1>{title}</h1>;
}

Something important to remember: useEffect() runs every single time a page renders, unless it includes dependencies. If you pass in an empty dependency array, useEffect() will only run on the initial render. If you pass in specific dependency values, it will only run any time time that value changes.

// Runs on every render.useEffect(() => {
document.title = title;
};
// Runs on first render.useEffect(() => {
document.title = title;
}, []);
// Runs when title is changed.useEffect(() => {
document.title = title;
}, [title]);

It’s especially important to include dependencies when you are using useEffect() to set your state, because if you forget to, it can cause an infinite loop as your component renders over and over. This can create major problems for your application.

Sometimes, we need to clean up or turn off our side effects. For example, if we built out a counter that uses the setTimeout() function, we may want it to stop the counting when our component has been unmounted so that it doesn’t continue to run in the background — using up valuable performance. You might also need to end subscriptions or reset event listeners.

To set up our clean up function, all we need to do is return a function from within useEffect().

const Counter = () => {
const [count, setCount] = useState(1)
useEffect(() => {
let counter = setTimeout(() => {
setCount((count) => count + 1);
}, 100);
return () => clearTimeout(counter)
}, []);
return <h1>Count: { count }</h1>;
}

In this case, our clean up function — which runs clearTimeout and resets our counter — will be called when the Counter component is unmounted. This is helpful to prevent errors or to clear out data when the user navigates to a different page.

You don’t need to use a clean up function, but it can be helpful in certain situations.

What makes the useEffect() hook particularly useful (and awesome) is that it replaces our need for traditional lifecyle methods like componentDidMount(), and componentDidUnmount() — all within a single function.

componentDidMount() is called after a component initially renders, which is very similar to calling useEffect() with an empty dependency array.

useEffect(() => {
document.title = title;
}, []);

componentWillUnmount() is called just before a component is removed from the DOM, which can be emulated using a clean up function.

useEffect(() => {
document.title = title;
return () => document.title = "";
}, []);

This is just another way that React Hooks allow for cleaner, more efficient, easier to read code. useEffect() allows us to leverage the side effects of Pure functions to pull in data from outside of our component while ensuring reliability and predictability.

--

--