JesusTheHun / storybook-addon-remix-react-router

Use your app router in your stories. A decorator made for Remix React Router and Storybook
Apache License 2.0
47 stars 11 forks source link

useParams cannot get routeParams as expected #5

Closed iamyoki closed 2 years ago

iamyoki commented 2 years ago

Reproduce

/** @type {import('@storybook/react').Meta} */
export default {
  component: PostDetail,
  parameters: {
    reactRouter: {
      routePath: '/posts/:postId',
      routeParams: {postId: 1},
    },
  },
};
export const Basic = (args) => {
  const {postId} = useParams();

  console.log(postId) // 👈 shows undefined in console

  return <PostDetailPage id={postId} />;
};

Expected

react-router's useParams should get the corresponding value from routeParams.

JesusTheHun commented 2 years ago

Hi 👋 Thank you for opening an issue. I see you haven't declared the decorator property in the story, have you declared it somewhere else ?

iamyoki commented 2 years ago

@JesusTheHun Oh! I forgot to mention that, I declared decorator in preview.js already.

export const decorators = [
  withRouter,
  (Story) => (
    <SWRConfig value={{fetcher: request.get}}>
      <Global styles={[rebase, layout]} />
      <Story />
    </SWRConfig>
  ),
];
JesusTheHun commented 2 years ago

Can you share a full reproduction example such as a repo ? Because I'm unable to reproduce the issue.

But just to be clear, is it normal that it is a story about PostDetail, but that the component is named Basic and renders PostDetailPage ?

iamyoki commented 2 years ago

@JesusTheHun Thank you for answering.

You are very careful, it's normal in my source actually, here I just wrote wrong.

Please check out my source code, and change this line into this

export default {
  component: PostDetail,
  parameters: {
    reactRouter: {
      routePath: '/posts/:postId',
      routeParams: {postId: 1},
    },
  },
};

export const Basic = (args) => {
 const {id} = useParams()
  return  <PostDetail id={id} />
};

Once you cloned and installed with yarn, use yarn scripts to start a storybook dev.

JesusTheHun commented 2 years ago

I've taken a look and this is not an addon issue. You cannot use a context (here the react router context) from a story function. If you move the useParams() inside a component you will get the expected parameter value.

For example, this will log the param value :

const LogParam = () => {
  const {postId} = useParams();
  console.log(postId);

  return null;
};

/** @type {import('@storybook/react').Story} */
export const Basic = (args) => {
  return (
    <>
      <LogParam />
      <PostDetail id='1' />
    </>
  );
};

I understand this is not what you were trying to achieve. I suggest you use a wrapper to inject the parameters :

const InjectParam = ({params, children}) => {
  React.Children.only(children);

  const routeParams = useParams();
  const paramsToInject = {};

  params.forEach((param) => {
    if (typeof param === 'string') {
      paramsToInject[param] = routeParams[param];
      return;
    }

    const [routeParamName, propertyName] = param;
    paramsToInject[propertyName] = routeParams[routeParamName];
  });

  return React.cloneElement(children, paramsToInject);
};
// Wrap your component inside your story function.

// <InjectParam params={['id']}> Using a simple string will map the `id` route param to the `id` property of the wrapper component.
// <InjectParam params={[['postId', 'id']]}> Using a tuple will map the `postId` route param to the `id` property.
// <InjectParam params={['anotherParam', ['postId', 'id']]}> You can mix both approaches

export const Basic = (args) => {
  return (
    <InjectParam params={[['postId', 'id']]}>
      <PostDetail />
    </InjectParam>
  );
};
iamyoki commented 2 years ago

@JesusTheHun Thank you so much, it seems reasonable.

JesusTheHun commented 2 years ago

If you consider your issue resolved, please close this issue.