描述错误
无法直接在故事中使用挂钩,失败的原因是Hooks can only be called inside the body of a function component
。
重现
示例代码:
import React from 'react'
import { storiesOf } from '@storybook/react'
const stories = storiesOf('Hooks test', module)
const TestComponent: React.FC = () => {
const [state, setState] = React.useState(5)
return (
<button onClick={() => setState(state + 1)}>
{state}
</button>
)
}
stories.add('this story works', () => <TestComponent />)
stories.add('this story fails', () => {
const [state, setState] = React.useState(5)
return (
<button onClick={() => setState(state + 1)}>
{state}
</button>
)
})
预期行为
第一个故事正常,第二个故事在初始渲染时失败
版本号@storybook/[email protected]
[email protected]
[email protected]
不确定这是一个错误。 我相信并且我可能是错的, stories.add
的第二个参数期望函数返回一个组件而不是实际的React组件。 尝试将功能组件移到外部,应该会成功。
例
function SomeComponent() {
const [blah] = React.useState('blah');
return <div> {blah}</div>;
}
stories.add('BlahComponent', () => <SomeComponent />);
我相信并且我可能是错的,
stories.add
的第二个参数期望函数返回一个组件而不是实际的React组件。
AFAIK并不重要,但始终值得尝试。 另外, @ sargant会引发什么错误? 故事书还是类型系统?
@Keraito不,我很确定错误是正确的。
这是因为我们是在react上下文之外调用该函数,因此故事书将这样调用该函数:
const element = storyFn();
不
const element = <StoryFn />
如果我们像这样启动它,可能会起作用。
现在, @ gabefromutah的建议很合理。
这是实际的代码行:
https://github.com/storybooks/storybook/blob/next/app/react/src/client/preview/render.js#L24
我认为,如果有人想通过实验来完成这项工作,那么这就是开始的地方。
@sargant @gabefromutah的建议对您
@ndelangen @gabefromutah的建议与我相信的最初“这个故事有效”的例子相同吗?
如果这不是错误,那么它仍然是避免使用第三方插件进行状态的有用增强。
如果我们像这样启动它,可能会起作用。
@ndelangen不能完全确定它将如何与其他某些插件(尤其是addon-info
交互,这些插件需要诸如底层渲染组件的props之类的信息。
我们也遇到过同样的问题,这是我们要解决的一个简单技巧。
stories.add('this story fails', () => React.createElement(() => {
const [state, setState] = React.useState(5)
return (
<button onClick={() => setState(state + 1)}>
{state}
</button>
)
}))
我同意,如果没有黑客的支持,那么对其进行原生支持会更好。 如果维护者也同意,那么也许我可以花些时间提出PR🙂。
@ kevin940726太棒了👍
将以下内容添加到我的.storybook / config.js中对我有用
addDecorator((Story) => <Story />)
我已经实现了类似的东西,但是我认为这会破坏很多插件,特别是用于输出道具文档的插件信息。 我已经决定使用钩子比该信息更重要(因为无论如何我都会分别生成该信息),但是我怀疑这是否适用于所有故事书。 不知道你如何在反应之外处理像api这样的钩子。
我试图在源代码中实现它,但是我很难使它与storyshot-addon一起使用。 我认为这将是一个重大突破,因为它将在每个快照中生成一个新的父节点。 由于我们无法控制用户选择的渲染器,因此我们无法深入到一个层次。 我认为我们可能不得不考虑其他选择,例如我们可以记录解决方案,并可能提供一些帮助API供用户选择。
将以下内容添加到我的.storybook / config.js中对我有用
addDecorator((Story) => <Story />)
@emjaksa能否请您提供一个
这是我针对此问题的解决方法:
import React, { useState } from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withInfo } from '@storybook/addon-info';
import SelectField from 'component-folder/SelectField';
/**
* special wrapper that replaces the `value` and `onChange` properties to make
* the component work hooks
*/
const SelectFieldWrapper = props => {
const [selectValue, setValue] = useState('');
return (
<SelectField
{...props}
value={selectValue}
onChange={e => {
setValue(e.target.value);
action('onChange')(e.target.value);
}}
/>
);
};
SelectFieldWrapper.displayName = 'SelectField';
const info = {
text: SelectField.__docgenInfo.description,
propTables: [SelectField],
propTablesExclude: [SelectFieldWrapper]
};
storiesOf('Controls/SelectField', module)
.addDecorator(withInfo)
// ... some stories
// this example uses a wrapper component to handle the `value` and `onChange` props, but it should
// be interpreted as a <SelectField> component
.add('change handler', () =>
<SelectFieldWrapper
id="employment-status"
placeholder="some placeholder"
value={//selectValue}
onChange={e => {
// setValue(e.target.value);
}}
/>, { info });
正如我所提到的,这仍然是一种解决方法,但是它确实有效,并且不会破坏info
插件。 (我尚未测试其他插件)
我的信息插件没有中断,只要我最后添加了Story装饰器。
import React from 'react'
import { configure, addDecorator } from '@storybook/react'
import { withInfo } from '@storybook/addon-info'
import { withKnobs } from '@storybook/addon-knobs'
const req = require.context('../src', true, /\.stories\.js$/)
function loadStories() {
req.keys().forEach(filename => req(filename))
}
addDecorator(
withInfo({
header: false,
}),
)
addDecorator(withKnobs)
addDecorator((Story) => (
<Story />
))
configure(loadStories, module)
另一个解决方法:
我有一个名为UseState
的实用程序组件,定义如下:
export const UseState = ({ render, initialValue }) => {
const [ variable, setVariable ] = useState(initialValue)
return render(variable, setVariable)
}
我在这样的故事中使用它:
.add('use state example', () => (
<UseState
initialValue={0}
render={(counter, setCounter) => (
<button onClick={() => setCounter(counter + 1)} >Clicked {counter} times</button>
)}
/>
)
但是我最喜欢@ kevin940726的解决方法
我无法让故事根据状态变化重新呈现。 强制重新渲染也不起作用。
虽然此代码对React Hooks很好用
storiesOf("Dropdowns", module).add("Basic", () => <DropdownBasicStory />);
它不适用于@storybook/addon-info
:
这使此替代方法不可用。 有任何想法吗? 故事书5.1.0-beta.0
@artyomtrityak您可以在addon-info
覆盖propTables
选项? 我将在即将到来的addon-docs
正确解决此问题: https :
@shilman还将包括<DropdownBasicStory />
吗?
@artyomtrityak我会看看我能做什么😆
嗨,大家好! 似乎最近在这个问题上没有发生太多事情。 如果仍有问题,意见或错误,请随时继续讨论。 不幸的是,我们没有时间解决所有问题。 我们随时欢迎捐款,因此,如果您想提供帮助,请向我们发送请求请求。 不活跃的问题将在30天后关闭。 谢谢!
如果还有其他人仍在理解这个问题,我发现问题似乎是在将prop类型添加到功能组件中。
const Dropdown = () => (
// component content
);
Dropdown.propTypes = {
...
}
由于某种原因,注释掉.propTypes
似乎可以使其正常工作。 不知道这是道具类型解析文档还是其他问题。
用钩子显示组件的源代码
function WithState({ children }) {
const [value, setValue] = React.useState([]);
return React.cloneElement(children, {
value,
onChange: event => setValue(event.target.value),
});
}
storiesOf(`${__dirname}`, module).add('Basic', () => (
<WithState>
<select value="[parent state]" onChange="[parent func]">
<option value="Australia">Australia</option>
<option value="Cambodia">Cambodia</option>
</select>
</WithState>
));
嗨,大家好! 似乎最近在这个问题上没有发生太多事情。 如果仍有问题,意见或错误,请随时继续讨论。 不幸的是,我们没有时间解决所有问题。 我们随时欢迎捐款,因此,如果您想提供帮助,请向我们发送请求请求。 不活跃的问题将在30天后关闭。 谢谢!
请谨慎使用上述React.createElement
和<Story />
方法,在某些情况下,我认为它们不太正确,因为您实际上是在重新创建react组件(故事组件),而不是返回呈现的元素(在文档中使用故事书config.js
使用story()
时)。
例如,当您的故事中有触发组件本地状态更新的Knobs.button
,单击该按钮后,您可能会创建一个新的故事组件,而不是更新当前组件的状态。
您可以使用此简单的代码段验证我的假设,在该代码段中,单击“旋钮”按钮后该值将不会更新。
storiesOf('Test', module).add('with text', () => {
return React.createElement(() => {
const [value, setValue] = React.useState(1);
Knobs.button('Increase', () => setValue(prev => prev + 1));
return <span>{value}</span>;
});
});
要使其生效,您可以执行以下操作:
function MyStory() {
const [value, setValue] = React.useState(1);
Knobs.button('Increase', () => setValue(prev => prev + 1));
return <span>{value}</span>;
}
storiesOf('Test', module).add('with text', () => <MyStory />);
因此,作为一种解决方法,我创建了一个包装器:
import {
DecoratorParameters,
Story,
StoryDecorator,
storiesOf as origStoriesOf,
} from '@storybook/react';
class ReactStory {
private readonly story: Story;
constructor(name: string, module: NodeModule) {
this.story = origStoriesOf(name, module);
}
public add(
storyName: string,
Component: React.ComponentType,
parameters?: DecoratorParameters
): this {
this.story.add(storyName, () => <Component />, parameters);
return this;
}
public addDecorator(decorator: StoryDecorator): this {
this.story.addDecorator(decorator);
return this;
}
public addParameters(parameters: DecoratorParameters): this {
this.story.addParameters(parameters);
return this;
}
}
new ReactStory('Test', module).add('with text', () => {
const [value, setValue] = React.useState(1);
Knobs.button('Increase', () => setValue(prev => prev + 1));
return <span>{value}</span>;
});
(可选)您可以模仿Storybook API来减少重构开销,并在解决问题后稍后再切换回。
export function storiesOf(name: string, module: NodeModule) {
return new ReactStory(name, module);
}
我可能是错的,如果是这样,请告诉我。 非常感激!
- 我刚刚发布了https://github.com/storybookjs/storybook/releases/tag/v5.2.0-beta.10,其中包含引用此问题的PR#7571。 立即升级以试用!
您可以在@next
NPM标签上找到此预发行版本。
解决此问题。 如果您认为还有更多事情要做,请重新打开。
@shilman谢谢! 您有一个如何使用它的代码片段示例吗?
@shiranZe当然可以: https :
@shilman谢谢!
但仍然出现错误:
在函数“ component”中调用React Hook“ useState”,该函数既不是React函数组件也不是自定义的React Hook函数
@shiranZe您正在使用最新的5.2-beta吗?
@shilman是的...
我在story / components / Menu / index.js上的代码:
const component =()=> {
const [buttonEl,setButtonEl] = useState(null)
const handleClick = (event) => {
console.log('the event', event)
setButtonEl(event.currentTarget)
}
return (
<>
<IconButton iconName={"menu-hamburger"} size="s" onClick={handleClick} color={"midGray"}></IconButton>
导出默认的[自述文件,组件];
并在story / components / index.js中:
storiesOf('Components', module)
.addDecorator(withKnobs)
.add('Text', withReadme(...Menu))
@shiranZe我想这是addon-readme
的问题-也许在那里提交问题?
谢谢@shilman的回复。 我不知道那是不是问题。 我尝试在没有addon-readme
仍然遇到相同的错误。 您是否有5.2-beta故事书的URL?
我试图看一下https://storybooks-official.netlify.com/
(other | demo / Button)
但我在那里找不到带有react hooks的示例。
我仍然遇到5.2.0-beta.30
的问题,而这对我来说也没有解决方法,因为我也使用了旋钮插件。
@shiranZe我们的netlify部署已关闭(抄送@ndelangen),但是您可以在next
分支上签出存储库并在那里尝试。
@sourcesoft我相信与旋钮相关的“预览挂钩”问题(cc @Hypnosphi)与该问题无关—它们唯一的共同点是挂钩的概念。
@shilman谢谢,我认为你是对的。 顺便说一句,我想出了如何通过反复试验修复它,更改了自定义钩子:
export const useField = (id, updateField) => {
const onChange = useCallback((event) => {
const {
target: { value },
} = e;
updateField(id, value);
};
return {
onChange,
};
}, []);
至
export const useField = (id, updateField) => {
const onChange = (event) => {
const {
target: { value },
} = e;
updateField(id, value);
};
return {
onChange,
};
};
基本上只是删除了useCallback
。 我不确定第一个版本是否是有效的钩子,但它曾经可以工作。 当错误显示Hooks can only be called inside the body of a function component
或具有多个版本的React时,也会有些混乱。
在上面的示例中,删除useCallback
,您是否真的在使用useField
中的任何钩子注册? 🤔
听起来好像在使用带有钩子的钩子有一些问题。 如果有人可以提供简单的复制,我很乐意看一下
@shilman您是否尝试过我之前发布的示例? 这是代码段:
storiesOf('Test', module).add('with text', () => {
return React.createElement(() => {
const [value, setValue] = React.useState(1);
Knobs.button('Increase', () => setValue(prev => prev + 1));
return <span>{value}</span>;
});
});
它使用旧的API,但应该易于转换为最新的API进行测试。 您可以从原始帖子中找到预期的行为。
@zhenwenc仅供参考,该代码有效,但中断了react-docgen-typescript-webpack-plugin
呈现的文档的使用。
解决方法似乎有些脆弱,并且与其他库冲突。 或者,有人有使用经验吗?: https :
对于那些在这里搜索错误消息的人:
将类组件更改为带有挂钩和useState
的功能组件时,从@storybook/addons
错误地导入时,出现此错误。 我需要它来自react
而不是@storybook/addons
...自动导入失败。
只需回答@orpheus的摘录请求
将以下内容添加到我的.storybook / config.js中对我有用
addDecorator((Story) => <Story />)
@emjaksa能否请您提供一个
diff --git a/.storybook/config.js b/.storybook/config.js
--- a/.storybook/config.js
+++ b/.storybook/config.js
@@ -1,6 +1,9 @@
-import { configure } from '@storybook/react';
+import { configure, addDecorator } from '@storybook/react';
+import React from 'react';
// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.js$/), module);
+
+addDecorator((Story) => <Story />);
结果(完整的.storybook/config.js
):
import { configure, addDecorator } from '@storybook/react';
import React from 'react';
// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.js$/), module);
addDecorator((Story) => <Story />);
我不确定这是否是在故事书中使用反应挂钩的最佳方法,但是将故事书包装在其自己的组件<Story />
组件中与"@storybook/react": "^5.2.6",
一起使用在这里。
在执行此操作之前,每当我更新一个旋钮(即:一个布尔值)时,它便会在第一个渲染上起作用,但随后会停止渲染。 上述解决方案已解决该问题。 顺便说一句,我不确定在故事书中使用react钩子是否是一个好习惯,如果可能的话就模拟所有内容,这样可能更好。
描述错误
无法直接在故事中使用挂钩,失败的原因是
Hooks can only be called inside the body of a function component
。重现
示例代码:
import React from 'react' import { storiesOf } from '@storybook/react' const stories = storiesOf('Hooks test', module) const TestComponent: React.FC = () => { const [state, setState] = React.useState(5) return ( <button onClick={() => setState(state + 1)}> {state} </button> ) } stories.add('this story works', () => <TestComponent />) stories.add('this story fails', () => { const [state, setState] = React.useState(5) return ( <button onClick={() => setState(state + 1)}> {state} </button> ) })
预期行为
第一个故事正常,第二个故事在初始渲染时失败版本号
@storybook/[email protected]
[email protected]
[email protected]
=====================================
不要使用箭头功能创建功能组件。
做为以下示例之一:
function MyComponent(props) {
const [states, setStates] = React.useState({ value: '' });
return (
<input
type="text"
value={states.value}
onChange={(event) => setStates({ value: event.target.value })}
/>
);
}
要么
//IMPORTANT: Repeat the function name
const MyComponent = function MyComponent(props) {
const [states, setStates] = React.useState({ value: '' });
return (
<input
type="text"
value={states.value}
onChange={(event) => setStates({ value: event.target.value })}
/>
);
};
如果“ ref”有问题(可能在循环中),则解决方案是使用forwardRef():
// IMPORTANT: Repeat the function name
// Add the "ref" argument to the function, in case you need to use it.
const MyComponent = React.forwardRef( function MyComponent(props, ref) {
const [states, setStates] = React.useState({ value: '' });
return (
<input
type="text"
value={states.value}
onChange={(event) => setStates({ value: event.target.value })}
/>
);
});
在MDX中如何实现?
但仍然出现错误:
在函数“ component”中调用React Hook“ useState”,该函数既不是React函数组件也不是自定义的React Hook函数
https://github.com/storybookjs/storybook/issues/5721#issuecomment -518225880
我有这个完全相同的问题。 解决方法是将命名故事输出大写。
从'react'导入React; 从'./Foo'导入Foo; 导出默认值{ 标题:“ Foo”; }; export const Basic =()=> <Foo />
文档说建议使用大写,但是如果您希望该警告消失,则有必要。
就我而言,问题是我忘记了导入正在使用的钩子。
将以下内容添加到我的.storybook / config.js中对我有用
addDecorator((Story) => <Story />)
@emjaksa Thanx! 经过测试并在Storybook v5.3上正常工作。
我遇到了同样的问题,我的功能性React组件有useState(value)
和value
没有从旋钮重新渲染新值,此问题是由useState引起的:
从React docs:
工作解决方案故事书v5.3:
更新preview.js
.storybook/preview.js
import React from 'react'; // Important to render the story
import { withKnobs } from '@storybook/addon-knobs';
import { addDecorator } from '@storybook/react';
addDecorator(withKnobs);
addDecorator(Story => <Story />); // This guy will re-render the story
并更新main.js
.storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.jsx'],
addons: [
'@storybook/preset-create-react-app',
'@storybook/addon-knobs/register', // Attention to this guy
'@storybook/addon-actions',
'@storybook/addon-links',
],
webpackFinal: async config => {
return config;
},
};
❓
有谁知道为什么这个调用装饰器使钩子起作用?
tsx
SomeComponent.decorators = [(故事)=>
@tmikeschu是因为<Story />
将代码包装在React.createElement
,这对于钩子是必需的。
@shilman谢谢!
最有用的评论
我们也遇到过同样的问题,这是我们要解决的一个简单技巧。
我同意,如果没有黑客的支持,那么对其进行原生支持会更好。 如果维护者也同意,那么也许我可以花些时间提出PR🙂。