reactjsnext.jsreact-testing-librarynextjs-15

React 19 'use', NextJS 15 async params and React testing-library


Using NextJS 14 and react testing-library, we previously used to test client components and client-only pages that take dynamic URL parameters like this:

type PageProps = {
    params: { date: string }
}

const SomePage: FC<PageProps> = ({ params }) => {
    const { date } = use(params);

...

it('renders expected week', async () => {
    render(<SomePage params={{ date: '2024-03-20' }} />);
    expect(screen.getByText('THIS WEEK')).toBeInTheDocument();
});

In nextJS 15, params is now a promise (https://nextjs.org/docs/app/guides/upgrading/version-15#async-request-apis-breaking-change), so as it's a client-component/page we're using React 19 'use' API in the page (https://react.dev/reference/react/use#reading-context-with-use):

type PageProps = {
    params: Promise<{ date: string }>
}

const SomePage: FC<PageProps> = ({ params }) => {
    const { date } = use(params);

...

What's the best way to test this with testing-library?

We can pass date as a Promise.resolve, but it never resolves:

it('renders expected week', async () => {
    render(<Page params={Promise.resolve({ date: '2024-03-20' })} />);
    expect(screen.getByText('THIS WEEK')).toBeInTheDocument();
});

whereas using a traditional 'useEffect' approach does:

const SomePage: FC<PageProps> = ({ params }) => {
const [date, setDate] = useState("");
useEffect(() => {
  params.then((resolvedParams) => {
    setDate(resolvedParams.date);
  });
});

(sandbox here: https://codesandbox.io/p/sandbox/determined-ramanujan-cmjqvw?file=%2Fsrc%2Findex.js%3A10%2C3-15%2C6)


Solution

  • Wrapping the render in act works for me:

    import { render, screen, act } from "@testing-library/react";
    //                       ^^^
    
    // ...
    
    it("renders message when promise resolves in use", async () => {
        await act(() =>
          render(
            <MessageComponentWithUse
              params={Promise.resolve({ message: "Hello world" })}
            />
          )
        );
    
        const message = await screen.findByText(
            /Here is the message: Hello World/i
        );
        expect(message).toBeInTheDocument();
    });
    

    (see updated example)