storybook 最大更新深度超过,取决于装饰器的顺序,

9fkzdhlc  于 4个月前  发布在  其他
关注(0)|答案(6)|浏览(71)

描述bug

我认为这个bug是react-final-form和storybook实现的组合效应,可能是当装饰器包含一个使用渲染属性的组件时出现的问题。也有可能是完全与react-final-form有关的问题,但我在使用该库的其他上下文中从未遇到过这个问题。下面的示例重现了错误信息。通过交换装饰器的顺序,即先有final-form装饰器,后有div-wrapper装饰器,可以解决这个问题。

复现步骤

初始化CRA项目
npx create-react-app example --template typescript
cd example
初始化storybook
npx sb init
安装final-form和react-final-form
yarn add final-form react-final-form
在stories目录中添加以下故事

// Test.stories.tsx
import React from 'react'
//eslint-disable-next-line
import { Story, Meta } from '@storybook/react/types-6-0'
import { Form, Field } from 'react-final-form'

export default {
  title: 'Test',
} as Meta

export const FinalFormTest = () => <Field name="test" component="input" />

const noop = () => {};

/**
* Change the order of these decorators to make maximum update depth 
* exceeded error dissappear.
*/
FinalFormTest.decorators = [
  (Story: Story) => (
    <div>
      <Story />
    </div>
  ),
  (Story: Story) => (
    <Form onSubmit={noop}>
      {({ handleSubmit }) => (
          <form onSubmit={handleSubmit}>
            <Story />
          </form>
      )}
    </Form>
  ),
]

预期行为

装饰器不应该依赖于它们定义的顺序。希望能够在不引发异常的情况下将final-form装饰器包裹在div装饰器中。

系统信息

系统:
操作系统:macOS 10.15.6
CPU:(4) x64 Intel(R) Core(TM) i5-4670 CPU @ 3.40GHz
二进制文件:
Node: 14.8.0 - /usr/local/bin/node
Yarn: 1.22.4 - /usr/local/bin/yarn
npm: 6.14.7 - /usr/local/bin/npm
浏览器:
Chrome: 84.0.4147.135
Safari: 13.1.2
npm包:
@storybook/addon-actions: ^6.0.20 => 6.0.20
@storybook/addon-essentials: ^6.0.20 => 6.0.20
@storybook/addon-links: ^6.0.20 => 6.0.20
@storybook/node-logger: ^6.0.20 => 6.0.20
@storybook/preset-create-react-app: ^3.1.4 => 3.1.4
@storybook/react: ^6.0.20 => 6.0.20

rvpgvaaj

rvpgvaaj1#

大家好!最近似乎没有太多关于这个问题的进展。如果还有问题、评论或错误,请随时继续讨论。遗憾的是,我们没有时间处理每一个问题。我们始终欢迎贡献,所以如果你想帮忙,请发送一个pull request。30天后未活跃的问题将被关闭。谢谢!

hsgswve4

hsgswve42#

你好,@slowselfip,我认为你是对的。你能解决这个问题吗?
@shilman 你和团队有机会深入研究这个问题吗?
我正在经历与react-hook-form相同的问题。我已经隔离了故事/故事以便于测试。在没有装饰器参与的情况下,将一个带有渲染属性的React组件嵌入到故事中是可以的。

import React from 'react'
// also exported from '@storybook/react' if you can deal with breaking changes in 6.1
import { Story, Meta } from '@storybook/react/types-6-0'

import {
  Controller,
  FormProvider,
  useForm,
  useFormContext
} from 'react-hook-form'

export default {
  title: 'Forms/Input',
  component: <input />,
  argTypes: {},
  // This is a workaround for the issue regarding "Maximum call stack size exceeded"
  // See https://github.com/storybookjs/storybook/issues/12747
  parameters: { docs: { source: { type: 'code' } } }
} as Meta

const reactHookFormProviderDecorator = (Story: Story) => {
  const rhFormMethods = useForm()
  return (
    <FormProvider {...rhFormMethods}>
      <form
        noValidate
        onSubmit={rhFormMethods.handleSubmit((v) => console.log(v))}
      >
        <Story />
        <input type='submit' />
      </form>
    </FormProvider>
  )
}

// This story fails because of the controller and its render method.
const TemplateFailsWithController: Story = (args) => {
  const { control } = useFormContext()
  return (
    <Controller
      contro={control}
      name='test'
      defaultValue='testabc'
      render={({ name, value, onChange, ref }) => {
        return (
          <input
            name={name}
            value={value}
            onChange={onChange}
            ref={ref}
            {...args}
          />
        )
      }}
    />
  )
}
export const TemplateFailsWithControllerStory = TemplateFailsWithController.bind(
  {}
)
TemplateFailsWithControllerStory.decorators = [reactHookFormProviderDecorator]
TemplateFailsWithControllerStory.args = {}

// This story works but with another structure ...
const TemplateWorksWithoutController: Story = (args) => {
  const { register } = useFormContext()
  return <input name='test' defaultValue='testabc' ref={register} {...args} />
}
export const TemplateWorksWithoutControllerStory = TemplateWorksWithoutController.bind(
  {}
)
TemplateWorksWithoutControllerStory.decorators = [
  reactHookFormProviderDecorator
]
TemplateWorksWithoutControllerStory.args = {}

const TemplateWorksWithControllerInside: Story = (args) => {
  const rhFormMethods = useForm()
  return (
    <FormProvider {...rhFormMethods}>
      <form
        noValidate
        onSubmit={rhFormMethods.handleSubmit((v) => console.log(v))}
      >
        <input
          name='test'
          defaultValue='testabc'
          ref={rhFormMethods.register}
          {...args}
        />
        <input type='submit' />
      </form>
    </FormProvider>
  )
}

export const TemplateWorksWithControllerInsideStory = TemplateWorksWithControllerInside.bind(
  {}
)
TemplateWorksWithControllerInsideStory.args = {}

顺便说一下:非常感谢这个很棒的工具;-)

2w2cym1i

2w2cym1i3#

在你的装饰器中,尝试使用 {Story()} 代替 <Story/>?

5kgi1eie

5kgi1eie4#

@gerardo-navarro 在你的装饰器中尝试使用 {Story()} 而不是 <Story/>?
@shilman 谢谢你的快速回复。
不幸的是,它不起作用。方法 const { control } = useFormContext() 返回 null,这不应该考虑到 FormProvider 被包裹在其中。既然返回了 null,以下代码会出错 => TypeError: Cannot read property 'control' of null
还有其他想法吗?

ig9co6j1

ig9co6j15#

嗨,伙计们,我和其他装饰器也有同样的问题:

import { gql } from "@apollo/client";
import { Args } from "@storybook/addons";
import React, { ReactElement } from "react";

import {
  FetchStatus,
  HeadlessStoryContext,
  Loader,
  pack,
  Prompt,
  withHeadless,
} from "storybook-addon-headless";

export default {
  title: "Examples/GraphQL",
  decorators: [
    withHeadless({
      graphql: {
        uri: "https://alameda-staging.herokuapp.com/graphql",
      },
    }),
  ],
  parameters: {
    headless: {
      Artworks: {
        query: pack(gql`
          {
            products {
              id
              title
              enable
              slug
              variants {
                title
                outOfStock
              }
            }
          }
        `),
        autoFetchOnInit: true,
      },
    },
  },
};

export const Artworks = (args: Args, { status, data }) => {
  console.log(args, status, data);
  return <h1>Hello</h1>;
};
mwngjboj

mwngjboj6#

我与react-hook-form FormProvider在Storybook的上一个版本中遇到了相同的问题

import { ReactElement } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { SnackbarProvider } from "notistack";
import { Story } from "@storybook/react";

import { PhotoInput, IPhotoInputProps } from "./index";

export default {
  title: "Photo",
  component: PhotoInput,
  decorators: [
    (Story: Story): ReactElement => (
      <SnackbarProvider>
          <FormProvider {...useForm({ defaultValues: { photo: [] } })}>
            <Story />
          </FormProvider>
      </SnackbarProvider>
    ),
  ],
};

const Template: Story<IPhotoInputProps> = args => <PhotoInput {...args} />;

export const Simple = Template.bind({});
Simple.args = {
  name: "photo",
};

顺便说一下,SnackbarProvider运行正常,不会出现任何错误
但是从FormProvider的Angular 来看-它也作为null出现,就像之前的评论一样

相关问题