尝试将 Storybook 与 react-admin 结合使用时出错 "useField must be used inside of a <Form> component"

Getting error trying to use use Storybook with react-admin "useField must be used inside of a <Form> component"

努力将 Storybook 与 react-admin 一起使用。我已经成功地将它与 React 和 material-ui 组件一起使用,但是当我添加 TextInput 时,出现此错误 "useField must be used inside of a component. So then I wrapped the TextInput inside a SimpleForm component and I get a second error: "Cannot read 属性 'history' of undefined ".

它超出了我目前的知识范围,我感到很沮丧。我确信这是一个简单的修复,我应该能够将 Storybook 与 react-admin 一起使用。任何人都可以告诉我这个秘密吗?这是我目前的简单故事:

import React from 'react';
import { SimpleForm, TextInput, required } from 'react-admin';
import { Button, Typography, Card, CardContent } from '@material-ui/core';

export default {
  title: 'Sources',
};

export const TestSource = () => {
  return (
    <SimpleForm>
      <TextInput
        source="test" label="Source Name" validate={required()} fullWidth margin="none"
        validate={required()}
      />
    </SimpleForm>
  )
}

如有任何帮助,我们将不胜感激。

更新: 感谢下面的@gstvg,添加 Admin 标签确实给出了一些结果。不确定我是否理解如何使它更有用(在浏览器中使用应用程序几乎与模拟在故事书中工作的表单一样多,但也许我还没有得到它。

这是新代码(必须为管理员添加一个 dataProvider 道具)和我看到的(至少我看到了一些东西,但表格是重复的,不确定它是否正确显示):

  return (
    <Admin dataProvider={dataprovider}>
      <SimpleForm>
        <TextInput
          source="test" label="Source Name" validate={required()} fullWidth margin="none"
          validate={required()}
        />
        <TextInput
          source="test" label="Next Field" validate={required()} fullWidth margin="none"
          validate={required()}
        />
      </SimpleForm>
    </Admin>

大多数 react-admin 组件都希望 props 被其他 react-admin 父组件传递

TextInput 期望的道具:

https://github.com/marmelab/react-admin/blob/b40229f6974a6ec586d9fc4b6116696df7b2b1d0/packages/ra-ui-materialui/src/input/TextInput.tsx#L88

并通过 SimpleForm:

https://github.com/marmelab/react-admin/blob/b40229f6974a6ec586d9fc4b6116696df7b2b1d0/packages/ra-ui-materialui/src/form/SimpleForm.js#L51

在这种情况下,TextInput 需要一个 SimpleForm 父级(或 TabbedForm、Filter 等),就像您所做的那样,而 SimpleForm 通常需要一个 Edit 或 Create 父级。

考虑到你遇到的错误,它们似乎与上面定义的预期缺失的道具无关,所以它们可能是由于超出了一些 react-admin 复杂的钩子范围或类似的东西而发生的,所以也许只是通过道具不会工作。

您可以尝试自己传递道具或将整个故事书嵌入到管理员中,就像这里所做的那样:

https://marmelab.com/blog/2019/01/17/react-timeline.html

更新您的发现:

我发现可行的最小设置是这样的:

import { Admin, Resource, Layout, SimpleForm, TextInput, required } from 'react-admin'

const NoLayout = props => (
    <Layout
        appBar={"span"}
        sideBar={"span"}
        menu={"span"}
        {...props}
    />
)

const Dashboard = props => (
    <SimpleForm>
        <TextInput
            source="test" label="Source Name" validate={required()} fullWidth margin="none"
            validate={required()}
        />
        <TextInput
            source="test2" label="Next Field" validate={required()} fullWidth margin="none"
            validate={required()}
        />
    </SimpleForm>
)

const App = () => {

    return (
        <Admin dataProvider={{}} menu={"span"} dashboard={Dashboard} layout={NoLayout}>
            <Resource name="test"/>
        </Admin>
    )
}

您甚至不需要真正的数据提供者。资源是为反应管理员而不是抱怨一个空的管理员。另外,我区分了输入源,两者相等,编辑一个输入会改变两者的值,我认为这不是你想要的

让我们从例子开始:

import React from 'react';
import { AdminContext, Resource } from 'react-admin';
import { createMuiTheme } from '@material-ui/core/styles';
import { ThemeProvider } from '@material-ui/styles';

import { createI18nProvider } from '../providers/createI18nProvider';
import { Edit } from './Edit';

const theme = createMuiTheme();

export default {
  title: 'resources/foo/edit',
  component: Edit,
  decorators: [
    (Story) => (
      <ThemeProvider theme={theme}>
        <AdminContext
          i18nProvider={createI18nProvider()}
          dataProvider={{
            getOne: async (_resource, params) => ({
              data: {
                id: params.id,
                name: 'Foo',
              },
            }),
            getList: async () => ({
              data: [
                {
                  id: '1',
                  name: 'Foo',
                },
              ],
              total: 1,
            }),
          }}
        >
          // you should list here all the resources that your story depends on
          <Resource name="Foo" intent="registration" />
          
          <Story />
        </AdminContext>
      </ThemeProvider>
    ),
  ],
};

const Template = (props) => <Edit {...props} />;

export const Default = Template.bind({});
Default.args = {
  id: '1',
  basePath: '/',
  resource: 'Foo',
};

当您使用低级别 <AdminContext /> 而不是 <Admin /> 时,您可以摆脱重复的表单视图。这也将禁用布局组件,以便您只呈现表单视图而无需任何额外界面(这对于故事来说是一个不错的决定)。此外,您应该渲染 ThemeProvider 以注入您的主题。

为了开始工作 dataProvider,您还需要为您在故事中使用的每个资源呈现 Resource。否则 react-admin 将忽略从 dataProvider 为未知资源返回的任何数据。


您还可以更进一步,将装饰器提取到辅助函数中,以便您可以轻松地在其他故事中重复使用装饰器,例如:

export const Default = Template.bind({});
Default.args = {
  id: '1',
  basePath: '/',
  resource: 'Foo',
};
Default.decorators = [
  createRaDecorator({
    resources: {
      Foo: {
        1: {
          id: '1',
          name: 'Foo',
        },
      },
    },
  }),
];

function createRaDecorator({ resources }) {
  return (Story) => (
    <ThemeProvider theme={theme}>
      <AdminContext
        i18nProvider={createI18nProvider()}
        dataProvider={{
          getOne: async (resource, params) => ({
            data: resources[resource][params.id],
          }),
          getList: async (resource) => ({
            data: Object.values(resources[resource]),
            total: Object.keys(resources[resource]).length,
          }),
        }}
      >
        {Object.keys(resources).map((resource) => (
          <Resource key={resource} name={resource} intent="registration" />
        ))}

        <Story />
      </AdminContext>
    </ThemeProvider>
  );
}