Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrating from StoriesOf Api to CSF - For Loop #9805

Closed
abt86croz opened this issue Feb 10, 2020 · 15 comments
Closed

Migrating from StoriesOf Api to CSF - For Loop #9805

abt86croz opened this issue Feb 10, 2020 · 15 comments

Comments

@abt86croz
Copy link

abt86croz commented Feb 10, 2020

Describe the bug
Can't loop and create dynamic CSF stories as used to when using the storiesOf API

Hey @shilman - we're following the CSF migration recommendation and I'm looking for examples where you can loop and generate multiple stories as we used to do using storiesOf

Like your forEach example here.

As you know, In the past we could do:

const stories = storiesOf('Heroes', module)

const storyOptions = [
  { name: 'thor', age: 21 },
  { name: 'ironman', age: 22 },
  { name: 'batman', age: 23 }
]

// loop and generate stories
for (const option of storyOptions) {
  stories.add(option.name, () => {
    return <p>This hero is {option.age}</p>
  })
}

Then we'd have stories populated as:
Screen Shot 2020-02-10 at 2 26 17 PM

How can we loop and have the same result as CSF without the storiesOf API?

export const heroes = () => {
for (const option of storyOptions) {
  // stories.add(option.name, () => {
   // ???
    return <p>This hero is {option.age}</p>
  })
}
}
@shilman
Copy link
Member

shilman commented Feb 10, 2020

@abt86croz AFAIK dynamic story generation is still only possible with the storiesOf API.

@gaetanmaisse
Copy link
Member

To add some explanation, it is impossible to do it with CSF for now as requirements to have working stories are strict:

  • a required default export
  • one or more named exports

Everything is fine with the default export however there is currently no way to have dynamic named exports because JS import and export have to be statically analyzable - i.e. known before executing the code.

Potential solution?

On CSF side: Allow named exports of array and/or map and/or key-value object of story functions in addition to "simple story function".

On Storybook side: Handle these new types of named exports and load stories accordingly.

@stale
Copy link

stale bot commented Mar 18, 2020

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

@stale stale bot added the inactive label Mar 18, 2020
@stale
Copy link

stale bot commented Apr 17, 2020

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

@stale stale bot closed this as completed Apr 17, 2020
@amcsi
Copy link

amcsi commented Jul 8, 2020

I'm interested in dynamically creating stories with loops as well. Please reopen this ticket.

@abt86croz
Copy link
Author

abt86croz commented Jul 10, 2020 via email

@Duder-onomy
Copy link

Please re-open this.
It seems like a reasonable thing to want. Personally, when writing stories, sometimes I like to loop through all prop combinations and create a story for each one.

@shilman What can we do to help?

@shilman
Copy link
Member

shilman commented Aug 18, 2020

That's the primary reason we're not deprecating the storiesOf format. It allows you to do exactly what you just described, and we recommend you use that for dynamically generated stories.

Can you tell us more about your use cases for dynamically generated stories? We're aware of the combinatorial stories use case and are considering supporting that natively to retain CSF's static analyzability.

@Duder-onomy
Copy link

^ For me,
The only use case would be the 'combinatorial' one you mention.

@stubar
Copy link

stubar commented Oct 24, 2020

I am defining my fixtures in an external file so they can be shared with tests an other composite components. With more complex components with many fixtures a CSF loop solution would be very favourable for me.

import * as fixtures from './fixtures';

import AuthorBio, { AuthorBioProps } from './AuthorBio';

export default {
  title: 'Components/AuthorBio',
  component: AuthorBio
} as Meta;

export const WithRole = (args: AuthorBioProps) => <AuthorBio {...args} />;
WithRole.args = fixtures.AuthorBioWithRole;

export const WithoutRole = (args: AuthorBioProps) => <AuthorBio {...args} />;
WithoutRole.args = fixtures.AuthorBioWithoutRole;

... // potentially many more boilerplate exports here

@viditganpi
Copy link

Has anyone found a workaround for this?

@max-pod
Copy link

max-pod commented Dec 23, 2021

I would be similarly interested.

@AdrianFahrbach
Copy link

Any news on the successor of StoriesOf?
In v7 we currently have to choose between autogenerated stories or autogenerated args (since those have to be disabled for storiesOf to still work).

The CSF Layout is very inflexible and makes autogenerating anything quite tedious. We even thought about generating our stories files with node.

@juancancela
Copy link

Definitely CSF looks like a step backwards in terms of flexibility to generate combinatorial of stories. Im on the fence of what to do to achieve this. Looks like generating stories as a post step is the only viable option.

@AdrianFahrbach
Copy link

AdrianFahrbach commented Feb 28, 2023

We are actually doing that now.
Our stories templates are called *.legend.tsx and contain two objects that get exported. A meta object with the stories metadata and an exportsToGenerate object with the future exports name as key and the value as value.

A ts-node script goes through all these *.legend.tsx files and generates *.generated.stories.tsx files that look exactly the same at first except that the parts that we don't want to transpile are replaced with placeholder strings. In our case that is the component in the meta object and the render function (since we don't want the full React component in the stories). For the render function we do that with a regex search that looks for the spaces before render: first to then find the corresponding closing tag. It looks like this:

const spacesBefore = tempFileContents.match(/^(\s+)*render:/m)[1];
const findRenderFunctionRegex = new RegExp(`^${spacesBefore}render:([\\S\\s]*^${spacesBefore}[)}]?,|.+,)`, 'm');

Our hero.generated.stories.tsx file then looks something like this:

export const meta = {
  title: 'Blocks/Hero',
  component: "%COMPONENT_PLACEHOLDER%",
};

export const exportsToGenerate = Object.keys(heroThemes).map(themeColor => ({
  [themeColor]: {
    args: {
      ...generatedArgs,
      backgroundColor: themeColor,
    }
    argTypes: generatedArgTypes,
    render: "%RENDER_PLACEHOLDER%",
  },
}));

In the next step the ts-node script goes through the *.generated.stories.tsx it just generated and imports the objects mentioned above like this:

const { exportsToGenerate, meta } = await import(storiesFilePath);

This is necessary so that e.g. our generatedArgs are turned into a plain text object and the reason why we are using ts-node.
The meta object gets changed to a default export and the exportsToGenerate are changed to separate exports. Our placeholder strings get replaced with the previously stored component and render function. The *.generated.stories.tsx file then gets replaced with the actually generated story.

It's absolutely disgusting and I strongly disencourage anyone from using anything like this in production, but in our case it works like charm. We only use Storybook for local development though since our NextJS project has gotten quite large (and slow).

You can take a look at the full files for inspiration here, but those aren't cleaned up for readability and are specific for our use case with NX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants