storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.68k stars 9.32k forks source link

[Bug]: Storybook preview hooks can only be called inside decorators and story functions. #29189

Open stevegreco opened 1 month ago

stevegreco commented 1 month ago

Describe the bug

When attempting to use a custom render function to create a Story for a controlled component, I am getting the following error.

Storybook preview hooks can only be called inside decorators and story functions.

This previously worked fine, so I am curious what changed.

My Story code is as follows. I am just trying to use a basic implementation of useState. The Story renders correctly, but as soon as I press the Button, this error is displayed.

From what I can tell, this is being caused by @storybook/addon-themes. You can see my setup in the reproduction.

// .storybook/preview.ts
import type { Preview, ReactRenderer } from '@storybook/react';
import { withThemeByDataAttribute } from '@storybook/addon-themes';

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
  decorators: [
    withThemeByDataAttribute<ReactRenderer>({
      themes: {
        light: 'light',
        dark: 'dark',
        auto: 'auto',
      },
      defaultTheme: 'light',
      attributeName: 'data-color-mode',
    }),
  ],
};

export default preview;
type Story = StoryObj<typeof meta>;

export const Controlled: Story = {
  args: {
    size: 'small',
    label: 'Button',
  },
  render: function Render(args) {
    const [pressed, setPressed] = React.useState(false);

    return (
      <>
        <Button {...args} onClick={() => setPressed(!pressed)} />
        <h1>{pressed.toString()}</h1>
      </>
    );
  },
};

Reproduction link

https://stackblitz.com/edit/github-rjewa6?file=.storybook%2Fpreview.ts

Reproduction steps

  1. Run the Storybook.
  2. Navigate to the Button -> Controlled Story
  3. Press the button

System

Storybook Environment Info:

  System:
    OS: macOS 15.0
    CPU: (14) arm64 Apple M3 Max
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.16.0 - ~/.nvm/versions/node/v20.16.0/bin/node
    npm: 10.8.1 - ~/.nvm/versions/node/v20.16.0/bin/npm
    pnpm: 9.6.0 - ~/Library/pnpm/.tools/pnpm/9.6.0/bin/pnpm <----- active
  Browsers:
    Chrome: 129.0.6668.58
    Safari: 18.0

Additional context

No response

lpillonel commented 1 month ago

Also facing the exact same issue, which appeared in version 8.3.0. For the moment, staying on 8.2.9 prevent the issue.

morewings commented 1 month ago

The same problem happens to me as well. Here is reproduction: chromatic preview, code

i-mo-gupta commented 1 month ago

Facing the same issue, it started happening with 8.3.0. It works fine till 8.2.9. I have the pretty much the same story example as given in description.

carlwelchyubico commented 1 month ago

Just notice this same issue today. Every story that has a useState is broken :(

peterfargo2 commented 1 month ago

Downgraded from 8.3.5 to 8.2.9 and useState works for me now.

morewings commented 1 month ago

I love the Empathy Backlog name. Sending warm vibes to Storybook comrades.

TheCodingGorilla commented 3 weeks ago

A not so ideal work around is:

export function Story() {
    return ComponentDecorator(Component);
}
morewings commented 2 weeks ago

A not so ideal work around is:

export function Story() {
    return ComponentDecorator(Component);
}

Sometimes Context is needed.

kasperpeulen commented 1 week ago

@yannbf This issue is really complicated and in fundamental way, this was broken for quite some time.

The issue seemed to have been aggravated by a RSC decorator change.

Here is a PR that should fix that issue: https://github.com/storybookjs/storybook/pull/26243

There will still be a problem when a user write a chain of decerator like this itself:

    withThemeByDataAttribute<ReactRenderer>({
      themes: {
        light: 'light',
        dark: 'dark',
        auto: 'auto',
      },
      defaultTheme: 'light',
      attributeName: 'data-color-mode',
    }),
    // We have something like this decorator now by default in React since 8.3.0
    // But you get the same error with a custom decorator, that doesn't use storyFn.
    (Story) => {
      return <Story />;
    },

Or even a custom decorator that uses the storybook context in some way:

    (storyFn) => {
      useParameter("bla", {});
      return storyFn();
    },
    (Story) => {
      return <Story />;
    },

This is a very weird issue. We are not yet at the bottom of this problem.

We might be able to prevent it with a custom applyDecorator, in a similar way that Vue has:

https://github.com/storybookjs/storybook/blob/1fdd2d6c675b81269125af5027e45a357c09f1fa/code/renderers/vue3/src/decorateStory.ts#L47-L80

And add a (S) => decorator in between each other decorator in the stack. After some experimenting it seems that React doesn't loose its react state in this way.