chevron-left chevron-right

Piotr Nalepa Blog webmasterski

Testing location change in useEffect in React

This version
Published
Author
Category
JavaScript
Feedback
Write to me · Post on X

This post is 4 years old. Browsers and tools may have changed since then, so check the code against current documentation before you use it.

Once I created a React component that was updating the app URL in useEffect based on some conditions. The logic was that whenever a user gets to the meetings list and none is selected yet, then by default a first item should be marked as selected. This information should be visible both in the UI (some special styles are applied then) and in the browser address bar (the id of a meeting should be a part of URL).

Section 1Solution

In my app I’m using react-router-dom and the useHistory hook. Looks at the code:

Example 1JSX
import { useHistory, useParams } from 'react-router-dom`;

export const MyComponent = ({ meetings }) => {
    const hsitory = useHistory();
    const { meetingId } = useParams();

    useEffect(() => {
        if (!meetingId) {
            history.push('meetings/' + meetings[0].id /* 1234 */);
        } 
    }, [meetingId, meetings]);

    return <div>MyComponent: {meetingId}</div>; 
}

In my tests I tried to do something like this:

Example 2JSX
it('adds the first meeting to URL', () => {
    renderComponent();

    expect(window.location.pathname).toBe('meetings/1234')
});

Obviously, the test was failing. Because this URL looks differently on first render. It looks like that: /meetings

I tried to use waitFor like here:

Example 3JSX
await waitFor(() => {
    expect(window.location.pathname).toBe('meetings/1234')
});

But it was giving false positives - it was not testing it properly.

Ultimately, I found a better solution, which is spying on history methods. Check the code below:

Example 4JSX
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';

let history;

const renderComponent = () => {
    history = createMemoryHistory();

    render(<Router history={history}><MyComponent /></Router>);
}

it('tests location changes', () => {
    renderComponent();

    const pushSpy = jest.spyOn(history, 'push');

    expect(pushSpy).toHaveBeenLastCalledWith(`meetings/1234`);
    expect(pushSpy).toHaveBeenCalledTimes(1);
});

This way I was able to test the URL changes in a satisfying way.

Section 2Summary

I hope you find this post useful. It’s a kind of Things-I-Learnt (TIL) post. If you have any questions, do not hesitate to ask. Happy coding!

About the author

Staff Software Engineer

I've been writing this blog since 2009 and building for the web professionally for 14 years. Today I lead the frontend of the Cosmos platform at Bishop Fox, where I've been since day one of the project. Before that I worked at OLX, KUDO, Auto1 and eZ Systems. Here I write about what I've learned in practice: JavaScript, TypeScript, React, CSS and frontend architecture. I've also spoken at conferences across Poland and Europe. Got a question or an idea for a post? Let me know!