storybookjs / storybook

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

[Bug]: SB/React diffing is very slow #27374

Open UltimateGG opened 3 months ago

UltimateGG commented 3 months ago

Describe the bug

This is a pretty niche case, but it is with a popular library (tanstack/react-table), and shows something underlying must be wrong.

The reason I say it is the React diffing is because in the reproduction url, uncommenting line 139 (Removing table from being passed in props) fixes the issue. table is a huge object with lots of functions (you can console.log it to see). This does not happen in normal React so it is not a react-table issue.

I doubt it is only for react-table, but this is the use case I found the issue in. It most likely happens from large deep objects (Needs further narrowing/testing though).

Just noting it down: In the profiler I also see a function called "hookify" a lot which comes from storybook.

Reproduction link

https://stackblitz.com/edit/github-8gtech

Reproduction steps

  1. Visit the URL above
  2. Open the "Demo" story
  3. In the "Filter emails..." text field, type "abc" and observe the delay/freezing
  4. Scroll down to the "Fixed" story
  5. Repeat step 3 but on the "Fixed" story
  6. Observe the instant feedback

System

Storybook Environment Info:

  System:
    OS: Windows 10 10.0.19045
    CPU: (8) x64 Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz
  Binaries:
    Node: 20.11.0 - C:\Program Files\nodejs\node.EXE
    npm: 9.8.1 - C:\Program Files\nodejs\npm.CMD <----- active
    pnpm: 9.0.6 - ~\AppData\Roaming\npm\pnpm.CMD
  Browsers:
    Edge: Chromium (125.0.2535.67)
  npmPackages:
    @storybook/addon-essentials: ^8.0.10 => 8.0.10
    @storybook/addon-interactions: ^8.0.10 => 8.0.10
    @storybook/addon-links: ^8.0.10 => 8.0.10
    @storybook/blocks: ^8.0.10 => 8.0.10
    @storybook/react: ^8.0.10 => 8.0.10
    @storybook/react-vite: ^8.0.10 => 8.0.10
    eslint-plugin-storybook: ^0.8.0 => 0.8.0
    storybook: ^8.0.10 => 8.0.10
    storybook-dark-mode: ^4.0.1 => 4.0.1

Additional context

This is a very odd bug because you can remove the props: any from the Demo story, and the issue is fixed. I need to be able to pass props to this story though.

### Tasks
yannbf commented 3 months ago

Hey @UltimateGG thank you so much for this detailed report and repro! I'll add some detailed information to assist the maintainers/contributors when working on a solution for this.

I investigated and figured out the root cause. Storybook's addon-docs has a feature to show the rendered JSX element like so:

image

This is the line of code that gets executed in order to provide a stringified version of the React components, using react-element-to-jsx-string: https://github.com/storybookjs/storybook/blob/b94ad4473564d8ca16f252f3b038e6aedf2df08e/code/renderers/react/src/docs/jsxDecorator.tsx#L173

The problem is a combination of two things:

  1. Your component's props are too huge and complex to parse, and react-element-to-jsx-string has trouble do to that in a performant way.
  2. Storybook injects a jsxDecorator which, unless the user opts out of it (I'll get to it later), will execute the "stringification" of the rendered component, in every render cycle, as can be seen here: https://github.com/storybookjs/storybook/blob/eac115d7f30975b57bef1f33979cdae9f0d8e7fc/code/renderers/react/src/docs/jsxDecorator.tsx#L263

It's quite unfortunate because react-element-to-jsx-string is being executed even if you're not using the component in a Storybook docs page.

To circumvent the issue, for now, you can opt-out of the jsxDecorator by adding the following parameter to your story (or to the default export):

Demo.parameters = {
  docs: {
    source: {
      type: 'code'
    }
  }
}

This should make sure the stringification won't happen and your interacting with your component will become instant again.