Reputation: 1721
Posting the solution to a problem I had a hard time finding albeit Sensei Googling skills.
Although my app with react-router worked without any problem Storybook threw the error "Invariant failed: You should not use <Link> outside <Router>".
Error: Invariant failed: You should not use <Link> outside a <Router>
at invariant (tiny-invariant.esm.js:11)
at react-router-dom.js:181
at updateContextConsumer (react-dom.development.js:19747)
at beginWork$1 (react-dom.development.js:20079)
at HTMLUnknownElement.callCallback (react-dom.development.js:358)
at Object.invokeGuardedCallbackDev (react-dom.development.js:410)
at invokeGuardedCallback (react-dom.development.js:463)
at beginWork$$1 (react-dom.development.js:25730)
at performUnitOfWork (react-dom.development.js:24631)
at workLoopSync (react-dom.development.js:24613)
Strange, as the app worked (and therefor no <Link> was used outside <Router>).
Upvotes: 54
Views: 40978
Reputation: 1342
Fabian's answer, the one that currently has the most votes, solves the problem only if you don't want to use React Router's data APIs in your stories.
Here's my modified version of Fabian's workaround that adds the ability to use React Router's data APIs, so it is possible to use data router-specific features like their extended Form
component:
import { createMemoryRouter, RouterProvider } from "react-router-dom";
import { addDecorator } from "@storybook/react";
addDecorator((story) => {
const router = createMemoryRouter([{ path: "/", element: story() }], {
initialEntries: ["/"],
});
return <RouterProvider router={router} />;
});
Upvotes: 0
Reputation: 1103
Here's what worked for me:
preview.js
file in your .storybook
folder.preview.js
file. import React from "react";
import { addDecorator } from "@storybook/react";
import { MemoryRouter } from "react-router";
addDecorator(story => <MemoryRouter initialEntries={['/']}>{story()}</MemoryRouter>);
Notes
"@storybook/react": "^5.3.13"
Edit: Update for Storybook V7 (Dec 2022)
Rename preview.js
to preview.tsx
(I guess it would work the same with jsx but my project is made in TypeScript)
addDecorator
function is not available for v7 anymore so you need to add it like this.
import React from "react";
import { MemoryRouter } from "react-router";
export const decorators = [
(Story) => (
<MemoryRouter initialEntries={['/']}>
<Story />
</MemoryRouter>
),
];
Upvotes: 89
Reputation: 1469
If you also want to use route path parameters, you will need to use the storybook decorator. user Web-ski and sidonaldson already showed how to add the router mock to storybook.
Here is my implementation of storybook router mock in react v 18
preview.js file
import { withRouter } from "storybook-addon-react-router-v6";
export const decorators = [withRouter];
component.story.js file
import Component from "./component";
export default {
component: Component,
title: "component with rout path and parameters",
parameters: {
reactRouter: {
routePath: "/root/:primary/:secondary",
routeParams: { primary: "firstparam", secondary: "secondparam" },
},
},
};
const Template = (args) => <Component {...args} />;
export const Default = Template.bind({});
Default.args = {
// ommited
};
This will give functions like useParams the following result:
const {primary, secondary} = useParams();
console.log('primary value: ' + primary)
console.log('secondary value: ' + secondary)
// outputs: primary value: firstparam
// outputs: secondary value: secondparam
Upvotes: -2
Reputation: 3124
If you want to do this for an individual component, rather than globally, then, in the story file, wrap the component in the memory router.
For example I have a header component that has a Link element so in my header.stories.tsx file I would change the following:
const Template: ComponentStory<typeof Header> = (args) => (
<Header {...args} />
);
to
const Template: ComponentStory<typeof Header> = (args) => (
<MemoryRouter>
<Header {...args} />
</MemoryRouter>
);
Upvotes: 1
Reputation: 25294
Further to Web-ski's answer. Add StoryRouter to your .storybook/preview.js
import StoryRouter from 'storybook-react-router';
addDecorator(StoryRouter());
It's now available globally, in every story.
Upvotes: 3
Reputation: 59
For version 6.1 of Storybook works storybook-router in the new code notation. Here is an example for a single component:
import React from 'react';
import StoryRouter from 'storybook-react-router';
import //your component// from //component location//
export default {
title: '//your component//',
component: //your component//,
decorators: [StoryRouter()],
};
export const Name = () => <//your component// />;
Upvotes: 5
Reputation: 319
This is what worked for me. Add in the Memory Router inside the decorators property.
import React from 'react';
import {MemoryRouter} from 'react-router-dom';
import //your component// from //component location//
export default {
title : //How you want your component to show in storybook eg: 'Components/Button'
component : //Specify the name of component that you imported eg: 'Button'
decorators : [(Story) => (<MemoryRouter><Story/></MemoryRouter>)] //Wrapping the story inside the router
};
Upvotes: 27
Reputation: 1721
The problem was Storybook rendering the individual stories. In this case the component using <Link>
was in fact rendered outside a <Router>
.
The solution was to wrap the individual stories with the <Router>
using addDecorator;
//config.js
//...
addDecorator(story => <Router>{story()}</Router>);
Upvotes: -2