尝试将 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 期望的道具:
并通过 SimpleForm:
在这种情况下,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>
);
}
努力将 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 期望的道具:
并通过 SimpleForm:
在这种情况下,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>
);
}