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

Source: Dynamic snippets includes decorators #12022

Closed
Quadriphobs1 opened this issue Aug 14, 2020 · 36 comments · Fixed by #29069
Closed

Source: Dynamic snippets includes decorators #12022

Quadriphobs1 opened this issue Aug 14, 2020 · 36 comments · Fixed by #29069

Comments

@Quadriphobs1
Copy link

Quadriphobs1 commented Aug 14, 2020

Describe the bug
When using decorator, viewing of the story would show the wrapper markup of the decorator and <No Display name /> for the story.

Expected behavior
It should only render the Story and not show the markup of the decorator

Screenshots

Code snippets

export default {
  title: "Atom/Input/Helper",
  component: Helper,


  decorators: [
    (Story: any) => (
      <div style={{ position: "relative" }}>
        <Story />
      </div>
    ),
  ],
};

const Template: Story<HelperProps> = (args) => (
  <Helper {...args} />
);

export const Default = Template.bind({});
Default.args = {
  message: "Please do this as soon as you do that.",
};

System:

Environment Info:
  Binaries:
    Node: 14.2.0 - ~/.nvm/versions/node/v14.2.0/bin/node
    Yarn: 1.22.4 - /usr/local/bin/yarn
    npm: 6.14.7 - ~/.nvm/versions/node/v14.2.0/bin/npm
  Browsers:
    Chrome: 84.0.4147.125
    Firefox: 77.0.1
    Safari: 13.1.2
  npmPackages:
    @storybook/addon-docs: ^6.0.6 => 6.0.6 
    @storybook/addon-essentials: ^6.0.6 => 6.0.6 
    @storybook/addon-knobs: ^6.0.6 => 6.0.6 
    @storybook/addon-links: ^6.0.6 => 6.0.6 
    @storybook/addon-notes: ^5.3.19 => 5.3.19 
    @storybook/addon-storysource: ^6.0.6 => 6.0.6 
    @storybook/addon-viewport: ^6.0.6 => 6.0.6 
    @storybook/addons: ^6.0.6 => 6.0.6 
    @storybook/preset-create-react-app: ^3.1.4 => 3.1.4 
    @storybook/react: ^6.0.6 => 6.0.6 
@shilman
Copy link
Member

shilman commented Aug 14, 2020

Unrelated, but this is probably not a good idea:

    @storybook/addon-notes: ^5.3.19 => 5.3.19 

@mauricioblum
Copy link

I'm having the exact same issue.

@vineethk123
Copy link

To add some info, if I use my decorator in the following format, it works:

decorators: [
  story => <div style={{height: "50vh"}}>{story()}</div>
]

Hope this might help in debugging 🙂

@Quadriphobs1
Copy link
Author

Actually that seems to work... but it still wraps the story in the provided decorator, isn't it meant to only show the story

@Tadimsky
Copy link

Actually that seems to work... but it still wraps the story in the provided decorator, isn't it meant to only show the story

Same here.

I see this:

<[object Object]
  value={{
    authenticated: {
      id: 'test'
    },
    setUser: () => {},
    signUp: function noRefCheck() {},
    user: {
      id: 'test'
    }
  }}
>
  <ApolloProvider client={[object Object]}>
    <MyComponent
          ...
    />
  </ApolloProvider>
</[object Object]>

When all I want to see is <MyComponent />

@shilman
Copy link
Member

shilman commented Sep 1, 2020

As a workaround you can set the docs.source.type story parameter to "code".

@Quadriphobs1
Copy link
Author

@shilman doesn't work

@shilman
Copy link
Member

shilman commented Sep 1, 2020

@Quadriphobs1 do you have a repro i can look at?

@darswright
Copy link

As a workaround you can set the docs.source.type story parameter to "code".

Where can this be set?

@shilman
Copy link
Member

shilman commented Sep 9, 2020

Something like:

export default {
  title: "Atom/Input/Helper",
  component: Helper,
  parameters: { docs: { source: { type: 'code' } } }
}

@Quadriphobs1
Copy link
Author

@shilman Using that produces no code available actually when the decorator is still provided

export default {
  title: "Atom/Component",
  component: Component,
  decorators: [
    (story: any) => {
      return <div>{story()}</div>;
    },
  ],
  parameters: { docs: { source: { type: 'code' } } }
};

@darswright
Copy link

When setting parameters as above "Show Code" window returns (args) => <Component {...args} /> for me. Same whether using a decorator or not.

@shilman shilman added P0 and removed tracked labels Sep 25, 2020
@shilman shilman changed the title Decorators wrapping rendering no name for component Source block: Dynamic source snippets includes decorators Sep 25, 2020
@shilman shilman changed the title Source block: Dynamic source snippets includes decorators Source: Dynamic snippets includes decorators Sep 25, 2020
@shilman shilman self-assigned this Sep 25, 2020
@shilman shilman added this to the 6.1 docs milestone Sep 25, 2020
@lewisccx
Copy link

I'm still facing this issue, any workaround available

@SimonErich
Copy link

Me too. What is the reason for that problem? Maybe a tsconfig or webpack problem?
The examples run perfectly fine though.

@paulcuth
Copy link

paulcuth commented Nov 10, 2020

It's a bit hacky, but I got around this by adding a decorator to wrap the story with known strings. Then I used transformSource to extract just the bit inside...

export default {
  title: 'Atom/Input/Helper',
  component: Helper,

  decorators: [
    story => (
      <>
        <span data-begin-source />
        {story()}
        <span data-end-source />
      </>
    ),
    (story: any) => <div style={{ position: 'relative' }}>{story()}</div>,
  ],

  parameters: {
    docs: {
      transformSource: source => {
        const [, output] =
          source.match(
            /<span data-begin-source \/>([\s\S]*?)<span data-end-source \/>/,
          ) ?? []
        return output
      },
    },
  },
}

Screenshot 2020-11-10 at 17 28 03

@4lejandrito
Copy link

Hi 👋 ,

I fixed this by replacing:

<Story />

by:

{Story()}

@shilman shilman modified the milestones: 6.1 docs, 6.2 docs Nov 24, 2020
@njcaballero
Copy link

njcaballero commented Nov 26, 2020

I got this to work but only globally by adding a decorator to the .storybook/preview.js file. Mine looks like this. I needed to include global styles available for all components but was running into the same issue. I get the same "No Display Name" issue when trying to add to decorators at the individual story level. Using Storybook version 6.1.4.

import React from 'react';

import { GlobalStyle } from '../src/styles/styles';

// Global decorator to apply the styles to all stories
export const decorators = [
  (Story) => (
    <>
      <GlobalStyle />
      <Story />
    </>
  ),
];

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
};

@KaiVandivier
Copy link

KaiVandivier commented Feb 11, 2021

are you using addon-storysource? If so, can you try removing it?

When I use docs.source.type = 'code', I get 'No code available' in the docs page, but removing addon-storysource makes the code snippets on the docs page work again. Is there a way to have both addon-storysource and the 'code' type source code snippets in the docs page?

Thanks for all your support on storybook @shilman!

UPDATE

By configuring addons-storysource in main.js in the following way, the code snippets in the docs page are working again:

module.exports = {
    addons: [
          ...
          {
              name: '@storybook/addon-storysource',
              options: { loaderOptions: { injectDecorator: false } },
          },
      ],
};

@shilman shilman modified the milestones: 6.2 docs, 6.2 candidates Mar 4, 2021
@shilman shilman added this to the 6.3 improvements milestone Apr 1, 2021
@shilman
Copy link
Member

shilman commented Apr 1, 2021

We want to address this in 6.3. If you want to contribute to Storybook, we'll prioritize PR review for any fixes here. And if you'd like any help getting started, please jump into the #support channel on our Discord: https://discord.gg/storybook

@Lance-Martin
Copy link

Lance-Martin commented Apr 5, 2021

I found a work around using transformSource that seems to solve the issue for stories using components with render props and doesn't require using a known string to search for in your decorator. The formatting isn't 100% perfect but it's usable.

<Story
            name="Themed MUI example"
            decorators={[FormWrapper]}
            parameters={{
                docs: {
                    transformSource: (source, snippet) => {
                        const parts = snippet.parameters.storySource.source.split('args => ')[1].split('>');
                        let wrapper = parts[0];
                        wrapper = wrapper.split('{...args}');
                        const args = snippet.parameters.args;
                        const keys = Object.keys(args);
                        const multKeys = keys.length > 1;
                        keys.forEach((arg, i) => {
                            const value =
                                typeof args[arg] == 'function'
                                    ? args[arg].toString()
                                    : JSON.stringify(args[arg], null, 4);
                            wrapper[1] = `${multKeys ? '\n    ' : ''}${arg}={${value}}${
                                multKeys && i === 0 ? '\n' + wrapper[1] : wrapper[1]
                            }`;
                        });
                        wrapper = wrapper.join('');
                        parts[0] = wrapper;
                        return parts.join('>');
                    }
                }
            }}
        />

This is assuming your story templates are written in the following manner:

export const PhoneFieldTemplate = args => (
  <PhoneField {...args} />
);

@gabiseabra
Copy link
Contributor

gabiseabra commented Apr 25, 2021

Storybook's printing of elements to jsx in dynamic mode has a couple of quirks causing these problems:
(1) It picks one point in the component tree and renders all of its children, somewhere before global and after component decorators, at jsxDecorator. Some examples here would be manageable if there was a way to configure this — an option to skip or filter nodes that get printed from the component tree, for example —, but AFAIK there is not.
(2) It all falls apart when function children are involved. Dynamic mode is configured to not render the contents of functions at all, making function children unviable. And there is also a bug in react-element-jsx-string preventing function children from being parsed.

Function children are tricky because the AST stops when it reaches one. There are no more children to traverse unless it is called and its return value parsed, which may not be desirable in all cases.
Alternatively, dynamic mode could just print functions' contents normally instead of parsing (or doing nothing as it does now), similar to how code mode works, but then decorators like: (story) => <Consumer>{story}</Consumer> wouldn't print anything useful.

I propose the following solution:
(1) Allow users to configure where printing begins by providing an option to change the position of jsxDecorator in a story's decorator list.

(2) Regarding function children, react-element-jsx-string provides an option to map function props to any other value (functionValue(fn: Function) option).
Assuming that a given prop is a pure function that returns react elements, you should be able map it to actual react elements safely with this option and resume parsing by just replacing it with its result.
But these are bold assumptions, and printing function props like this would be a breaking change, so there should be a way opt-in to this while keeping the current behavior as default. This could be done by "flagging" functions that should be rendered and parsed with an extra parameter:

const ContextDecorator = (story) => <Consumer>{dynamic(ctx => story(ctx))}</Consumer>
// ...
const dynamic = (fn) => Object.assign((...args) => fn(...args), { sourceType: 'dynamic' })

Other modes could also be configurable:

type Mode = "dynamic" | "code" | "none"

const docMode = (mode: Mode, fn: Function) => Object.assign((...args) => fn(...args), { sourceType: mode })

Then react-element-jsx-string could be configured as such (not tested):

{
  showFunctions: true,
  functionValue(fn) {
    const sourceType = fn.sourceType || 'none' // This is the current behavior for all functions
    switch(sourceType) {
      case 'dynamic': return fn({})
      case 'none': return () => {}
      case 'code': return fn
    }
  }
}

@ghost
Copy link

ghost commented May 19, 2021

#12022 (comment)

This worked for me.

@shilman
Copy link
Member

shilman commented May 24, 2021

Huzzah!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.3.0-alpha.41 containing PR #14652 that references this issue. Upgrade today to the @next NPM tag to try it out!

npx sb upgrade --prerelease

Closing this issue. Please re-open if you think there's still more to do.

@shilman shilman closed this as completed May 24, 2021
@sep8
Copy link

sep8 commented Jun 11, 2021

Hi @shilman, I have upgraded to the latest version(6.3.0-rc.2), but this problem still exists:

Here is my stories:

export default {
  title: 'Components/Badge',
  component: Badge,
  decorators:[(Story) => <div className='wrapper'><Story/></div>],
  parameters: {
    docs: {
      source: {
        type: 'dynamic'
      }
    }
  },
} as Meta

const Template: Story<BadgeProps> = (args) => (
  <Badge {...args}>
    <Box size='sm' />
  </Badge>
)

export const Basic = Template.bind({})

The code in docs:
image

@shilman
Copy link
Member

shilman commented Jun 11, 2021

@Mike-Han you need to opt-out

        docs: {
          source: {
            type: 'dynamic',
            excludeDecorators: true,
          },
        },

@sep8
Copy link

sep8 commented Jun 15, 2021

@shilman It works well, thanks!

@kinoli
Copy link

kinoli commented Jun 17, 2022

Solution: My issue doesn't seem to be decorator related but seeing No Display Name. So, replacing that text with the last segment of the title is a simple solution and most, if not all, titles should be named the same as the component.

This fixes it for me.

docs: {
    transformSource: (source, { title }) => {
      return source.replaceAll('No Display Name', title.substring(title.lastIndexOf('/') + 1));
    }
}

@365kim
Copy link

365kim commented Oct 27, 2022

Only @kinoli's solution worked for me. Thank you 👍

@ptu14
Copy link

ptu14 commented Jul 28, 2023

this solution does not work with Angular with compdoc

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