Background
I was working on integrating Storybook process into legacy system using next. The thing is, some of the components are dependent on the useRouter()
hook from next/navigation
. I'm planning to document how to work with those and fix getting errors like:
Error: invariant expected app router to be mounted
Approach
TL;DR Use context provider with mock value. Like:
<AppRouterContext.Provider value={mockRouter}>
<Component {...args} />
</AppRouterContext.Provider>
Setup
Import dependency like this on your Storybook. Example, I have this page component I want to render on storybook.
// src/app/login/page.tsx
import { useRouter } from 'next/navigation';
const REGISTER_ROUTE = '/register';
export default function LoginPage() {
const router = useRouter(); // The offending line
return (
<div>
<button onClick={() => router.push(REGISTER_ROUTE)}>
Register
</button>
</div>
)
}
With Storybook story like:
// src/stories/LoginPage.stories.tsx
import type { Meta, StoryObj } from 'storybook/react';
import Component from '@app/login';
const meta = {
title: 'Login Page',
component: Component,
tags: ['autodocs'],
} satisfies Meta<typeof Component>;
export default meta;
type Story = StoryObj<typeof meta>;
export const DefaultView: Story = {};
This won't run and will fail.
Solution
Update the storybook and wrap component in next AppRouterContext
.
// src/stories/LoginPage.stories.tsx
import type { Meta, StoryObj } from 'storybook/react';
+ import {
+ AppRouterContext,
+ AppRouterInstance,
+ } from 'next/dist/shared/lib/app-router-context';
import Component from '@app/login';
const meta = {
title: 'Login Page',
component: Component,
tags: ['autodocs'],
} satisfies Meta<typeof Component>;
+ // I tried empty functions, but I think mock function would
+ // be better? See: `vi.fn()`
+ const mockRouter: AppRouterInstance = {
+ back: () => {},
+ forward: () => {},
+ prefetch: () => {},
+ push: () => {},
+ refresh: () => {},
+ replace: () => {},
+ }
export default meta;
type Story = StoryObj<typeof meta>;
- export const DefaultView: Story = {};
+ export const DefaultView: Story = {
+ render: (args) => )
+ <AppRouterContext.Provider value={mockRouter}>
+ <Component {...args} />
+ </AppRouterContext.Provider>
+ ),
+ };
Top comments (0)