Dave Sag
Dave Sag

Reputation: 13486

React + Redux + Storybook: How to use connected react router's useParams when writing storybook stories?

I have a react component that grabs an id from the route and uses that to load some data and populate the redux state.

I am using useParams from 'react-router' to do this.

import { useParams } from 'react-router'

import { usePreload } from './hooks'
import Display from './Display'

const Overview = () => {
  const { id } = useParams()
  const { data } = usePreload(id) // uses useEffect to preload the data with the given id

  return <Display data={data} />
}

export default Overview

I've got a story

import Overview from './Overview'

import preloadData from './decorators/preloadData'

export default {
  title: 'Redux/scenes/Overview',
  decorators: [preloadData()],
  component: Overview,
  argTypes: {}
}

const Template = args => <Overview {...args} />

export const Default = Template.bind({})

The preloadData decorator is simply

import { usePreload } from '../hooks'
import { data } from './fixtures'

const Loaded = ({ children }) => {
  useSubmissionsPreload(data.id) // loads the site data into the state

  return <>{children}</>
}

const preloadData = () => Story => (
  <Loaded>
    <Story />
  </Loaded>
)

export default preloadData

The code all works fine when actually running in the site but when running within a story there is no :id in the path for useParams to pick up.

For now I am just going to skip this story and just test the Display component, but the completist in me demands to know how to get this to work.

Upvotes: 13

Views: 8664

Answers (4)

Ashan
Ashan

Reputation: 19748

I've faced the same problem with Storybook 6.3+ and React Router 6.00-beta and had to wrap the <Route> with <Routes></Routes> for it to work.

import React from "react";
import { Meta } from "@storybook/react";
import MyComponent from "./MyComponent";
import { MemoryRouter, Routes, Route} from "react-router";

export default {
    title: "My Title",
    component: MyComponent,
    decorators: [(Story) => (
        <MemoryRouter initialEntries={["/path/58270ae9-c0ce-42e9-b0f6-f1e6fd924cf7"]}>
           <Routes>
             <Route path="/path/:myId" element={<Story />}/>
           </Routes>
        </MemoryRouter>)],
} as Meta;

export const Default = () => <MyComponent />;

Upvotes: 5

Raphael
Raphael

Reputation: 1812

Faced the same issue and completed as below

export default {
      title: 'Common/Templates/Template Rendering',
      component: CasePage
    }
    // 👇 We create a “template” of how args map to rendering
    const Template: Story<any> = (args: any) => {
      const { path } = args
      return (
        <MemoryRouter initialEntries={path}>
          <Route
            component={(routerProps: any) => <CasePage {...routerProps} />}
            path="/dcp/:caseId"
          />
        </MemoryRouter>
      )
    }
    
    export const TemplateBoxesRendering = Template.bind({})
    TemplateBoxesRendering.args = { path: ['/dcp/FX77777'] }
    
    export const TemplateBoxes = Template.bind({})
    TemplateBoxes.args = { path: ['/dcp/FX22222'] }

Upvotes: 0

astroknaut
astroknaut

Reputation: 136

I also had the problem and the comment from De2ev pointed me in the right direction. It did however not work directly and I had to make slight changes. In the end it worked with the following code:

import React from "react";
import { Meta } from "@storybook/react";
import MyComponent from "./MyComponent";
import { MemoryRouter, Route} from "react-router-dom";

export default {
    title: "My Title",
    component: MyComponent,
    decorators: [(Story) => (
        <MemoryRouter initialEntries={["/path/58270ae9-c0ce-42e9-b0f6-f1e6fd924cf7"]}>
            <Route path="/path/:myId">
                <Story />
            </Route>
        </MemoryRouter>)],
} as Meta;

export const Default = () => <MyComponent />;

Upvotes: 12

De2ev
De2ev

Reputation: 39

We have faced similar challenge when trying to create storybook for one of the pages. We found solution published on Medium -> link. All credits and special thanks to the author.

Solution is using MemoryRouter available in react-router.

In our solution we used storybook Decorators which return the story wrapped by MemoryRouter and Router ->

return ( <MemoryRouter initialEntries={["/routeName/param"]} <Route component={(routerProps) => <Story {...routerProps} />} path="/routeName/:paramName"/> </MemoryRouter>)

I hope this helps everyone who experienced the same challenge.

Upvotes: 3

Related Questions