If you’re familiar with React, then you’ve probably heard of the useEffect hook. It’s one of the most powerful and commonly used hooks in React, and it allows you to perform side effects in your components, such as fetching data from an API or setting up event listeners.

In Next.js, the useEffect hook is just as important, and it can be used in a variety of ways to enhance your application’s functionality and performance. If you’re using vanilla React, you might want to check out this article.

In this article, we’ll take a closer look at the useEffect hook in Next.js, exploring its syntax, use cases, and best practices. We’ll also provide some practical examples and code snippets to help you get started with using the useEffect hook in your Next.js applications.

So, whether you’re a seasoned React developer or new to the world of web development, read on to learn more about the useEffect hook in Next.js and how it can help you build better, more performant applications.

useEffect

For the short version, the useEffect hook can be used to manage side-effects in your React components. It’s used to synchronise your component with some sort of external data source. Here’s what it looks like:

    useEffect(function setupFunction() => {
        
        return cleanupFunction;
    }, dependencyArray)

We have three components. Firstly the setup function; this gets called every time your useEffect gets triggered. Inside the setup function, you can return a cleanup function, which gets called every rerender. Then there’s the dependency array, which controls when your useEffect gets triggered.

Dependency Array

The dependency array is the second parameter of the useEffect hook. It controls when your useEffect gets re-ran. If you provide:

  • Nothing – The effect runs after every render
  • [ ] (Empty array) – The effect runs after the first render
  • [foo, bar] – The effect runs after the first render, and whenever foo or bar changes

Using these you can synchronise effects to run whenever you like.

Most commonly you’ll see useEffect functions with either an empty array, so the hook only runs once, or with variables in the dependency array, to keep a component synchronised.

Empty Array

With an empty array, your useEffect only runs on the first render. You might use this for example when fetching data that doesn’t depend on any parameters.

Here’s a pattern you might see often for data fetching:

export default function Effect() {

    const [data, setData] = useState();

    useEffect(() => {
        fetchData().then((response) => setData(response))
    }, []);
    
    return <div>{data}</div>
}

We use our useEffect to start the data fetching when our component first renders. In our case the data doesn’t depend on any state or events, so we just need to fetch the data once our component has rendered.

Multiple

With items in the dependency array, the hook runs on the first render, and whenever any of the values inside the dependency array changes.

If we take our previous example:

export default function Effect() {
    
    const [user, setUser] = useState();
    const [data, setData] = useState();
    
    useEffect(() => {
        fetchUser(user).then((response) => setData(response));
    }, [user]);

    return <div>{data}</div>;
}

Imagine we have some component that displays user profiles, think something along the lines of Tinder swipe cards. In that case, the current user might be stored in state, in which case whenever we swipe, we need to fetch the new user.

No Dependencies

You might not see this option as much, just because it’s more common to have your useEffect hook synchronised with some external state, or to just run once. Additionally, it’s much easier to just place your logic inside the body of your component, which will run every render as well.

If we take our previous example again:

export default function Effect({user}: {user: string}) {
    const [friends, setFriends] = useState();


    useEffect(() => {
        fetchUser(user).then((response) => setFriends(response.friends));
    });

    return <div>{friends}</div>;
}

Here’s a possible example where you might want to have no dependencies in your useEffect array. In this case we have our imaginary social media, but now we’re taking in the user as a prop, and then fetching the friends list for our user in our component. Every time the user changes and our component re-renders, we re-fetch the list of users.

Note that in a real-world scenario, it’s important to ensure that the effect function is not doing something expensive or time-consuming. In this example, our imaginary effect function could be a small API request, or something more complex, in which case it may be necessary to include a dependency array just to ensure that it’s not executed unnecessarily.

Cleanup Functions

When using the useEffect hook in Next.js, it’s important to remember that it can potentially cause side effects and memory leaks if not used properly. That’s where the cleanup function comes in.

The cleanup function is a way to clean up any resources or effects created by the hook. It’s executed when the component is unmounted, before the new effect is applied (if there is one). This means that you can use it to cancel any ongoing network requests, unsubscribe from event listeners, or perform any other necessary cleanup.

To use the cleanup function, simply return a function from the effect callback. This function will be called when the component unmounts, allowing you to clean up any resources that were created during the effect.

Let’s take a look at a practical example:

export const sizes = {
    sm: '640px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
    '2xl': '1536px',
};
export const useMediaQuery = (screen) => {
    const [matches, setMatches] = useState(false);

    useEffect(() => {
        const query = `(min-width: ${sizes[screen]})`;
        const media = window.matchMedia(query);
        console.log(media);
        if (media.matches !== matches) {
            setMatches(media.matches);
        }
        const listener = () => setMatches(media.matches);
        window.addEventListener('resize', listener);
        return () => window.removeEventListener('resize', listener);
    }, [matches, screen]);

    return matches;
};

Here’s a hook used to see if a screen matches a given media query. We can extract most of the logic, here’s the important parts:

export const useMediaQuery = (screen) => {
    const [matches, setMatches] = useState(false);

    useEffect(() => {
        const listener = () => setMatches(media.matches);
        window.addEventListener('resize', listener);
        return () => window.removeEventListener('resize', listener);
    }, [matches, screen]);

    return matches;
};

We’re adding an event listener here to call our callback listener function whenever the screen gets resized. In a regular JavaScript program, you might not have to worry about removing the event listener. Your page gets loaded, the event listener gets added, and the event listener might exist for the whole time that the page is loaded.

In Next.js however, the component that uses this hook might get remounted or re-rendered several times. Every time that happens, that useEffect gets called, and our event listener gets added again. We need to return a cleanup function so that every time this does happen, the old one gets removed first.

Conclusion

To sum it up, the useEffect hook is an extremely useful hook used for synchronising your components with some effect, or external state, and using that to perform side effects in your component. It works by providing it a function to perform your side effect in, which can also return an optional clean up function to clean up any of your side effects, e.g. event listeners.

If you liked this article, or if you had any questions, feel free to leave a comment below!

Avatar photo
👋 Hey, I'm Omari Thompson-Edwards
Hey, I'm Omari! I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, reach out on Twitter at @marile0n

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.