Опишите ошибку
Невозможно использовать крючки непосредственно в истории, ошибка 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 Нет, я почти уверен, что ошибка верна.
Это потому, что мы вызываем функцию вне контекста реакции, он же Storybook вызовет функцию следующим образом:
const element = storyFn();
не
const element = <StoryFn />
Вполне возможно, если бы мы инициировали это так, это могло бы сработать.
На данный момент совет
Вот фактическая строка кода:
https://github.com/storybooks/storybook/blob/next/app/react/src/client/preview/render.js#L24
Я думаю, что если кто-то хочет поэкспериментировать, чтобы заставить эту работу работать, то с этого и следует начать.
@sargant подходит ли вам предложение
Предложение @ndelangen @gabefromutah совпадает с моим первоначальным примером "эта история работает", как я полагаю?
В случае, если это не ошибка, это может быть полезным улучшением, позволяющим избежать использования сторонних плагинов для состояния.
Вполне возможно, если бы мы инициировали это так, это могло бы сработать.
@ndelangen не совсем уверен, как это будет взаимодействовать с другими определенными надстройками, особенно с addon-info
, которым нужна информация, такая как реквизиты базового визуализированного компонента.
Мы столкнулись с той же проблемой, и это простой способ ее решения.
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 />)
Я реализовал нечто подобное, но я думаю, что это ломает кучу надстроек, в частности, addon-info для вывода документации по опорам. Я решил, что использование хуков более важно, чем эта информация (так как я все равно генерирую ее отдельно), но я сомневаюсь, что это применимо ко всему сборнику рассказов в целом. Не знаю, как бы вы справились с хуками вроде api вне React.
Я пытался реализовать это в исходном коде, но мне трудно заставить его работать с помощью 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 Вы можете переопределить параметр propTables
в addon-info
? Я буду решать эту проблему в следующих addon-docs
: https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a
@shilman, будет ли он также включать источник <DropdownBasicStory />
?
@artyomtrityak Я посмотрю, что я могу сделать 😆
Всем привет! Похоже, что в последнее время в этом вопросе особо ничего не происходит. Если все еще есть вопросы, комментарии или ошибки, пожалуйста, продолжайте обсуждение. К сожалению, у нас нет времени разбираться в каждой проблеме. Мы всегда открыты для участия, поэтому, если вы хотите помочь, отправьте нам запрос на включение. Неактивные вопросы будут закрыты через 30 дней. Благодаря!
Если кто-то еще получает это, я обнаружил, что проблема заключается в добавлении типов опор в функциональный компонент.
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 />
, я думаю, что в некоторых случаях они не совсем правильны, потому что вы фактически воссоздаете компоненты реакции (компонент истории) вместо того, чтобы возвращать визуализированные элементы (при использовании story()
в сборнике рассказов config.js
как показано в документации).
Например, если в вашей истории есть 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>;
});
При желании вы можете имитировать 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 спасибо !! у вас есть пример фрагмента кода, как его использовать?
@shilman спасибо!
но все еще получаю ошибку:
React Hook "useState" вызывается в функции "component", которая не является ни компонентом функции React, ни пользовательской функцией React Hook.
@shiranZe Вы используете последнюю версию 5.2-beta?
@shilman да ...
мой код в Stories / components / Menu / index.js:
const component = () => {
const [buttonEl, setButtonEl] = useState (ноль)
const handleClick = (event) => {
console.log('the event', event)
setButtonEl(event.currentTarget)
}
return (
<>
<IconButton iconName={"menu-hamburger"} size="s" onClick={handleClick} color={"midGray"}></IconButton>
экспорт по умолчанию [readme, компонент];
и в Stories / components / index.js:
storiesOf('Components', module)
.addDecorator(withKnobs)
.add('Text', withReadme(...Menu))
@shiranZe Думаю, это проблема с addon-readme
- может быть, там о проблеме написать?
Спасибо @shilman за ответ. Не знаю, проблема ли в этом. Я пробовал без addon-readme
но все равно получаю ту же ошибку. у вас есть URL книги рассказов 5.2-beta?
я попытался взглянуть на https://storybooks-official.netlify.com/
(другое | демо / кнопка)
но я не могу найти там пример с реагирующими хуками.
У меня все еще есть проблема с 5.2.0-beta.30
и дело в том, что ни один из обходных путей у меня не сработал, так как я тоже использую аддон кнопок.
@shiranZe наше развертывание netlify отключено (cc @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://github.com/Sambego/storybook-state
Для тех, кто ищет здесь сообщение об ошибке:
Я получил эту ошибку, когда при изменении компонента класса на функциональный компонент с помощью хуков и useState
неправильно импортировались из @storybook/addons
. Мне нужно, чтобы это происходило из react
а не из @storybook/addons
... сбой автоматического импорта.
Просто отвечаю на запрос сниппете
Добавление следующего в мой .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",
.
До этого, всякий раз, когда я обновлял ручку (например, логическое значение), она работала при первом рендеринге, но затем прекращала рендеринг. Вышеупомянутое решение исправило это. Кстати, я не уверен, что использовать в сборнике рассказов перехватчики реакции - хорошая практика, просто поиздевайтесь над всем, если возможно, так, наверное, лучше.
Опишите ошибку
Невозможно использовать крючки непосредственно в истории, ошибка
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 })}
/>
);
});
как этого можно достичь в многомерных выражениях?
но все еще получаю ошибку:
React Hook "useState" вызывается в функции "component", которая не является ни компонентом функции React, ни пользовательской функцией React Hook.
https://github.com/storybookjs/storybook/issues/5721#issuecomment -518225880
У меня была такая же проблема. Исправление состоит в том, чтобы использовать экспорт именованных историй.
импортировать React из React; импортировать Foo из './Foo'; экспорт по умолчанию { название: 'Foo'; }; экспорт const Basic = () => <Foo />
В документах говорится, что использование заглавных букв рекомендуется, но это необходимо, если вы хотите, чтобы это предупреждение исчезло.
В моем случае проблема заключалась в том, что я забыл импортировать крючок, который использовал.
Добавление следующего в мой .storybook / config.js сработало для меня
addDecorator((Story) => <Story />)
@emjaksa Спасибо! Протестировано и отлично работает на Storybook v5.3.
У меня была такая же проблема, мой функциональный компонент реакции имеет useState(value)
и value
не повторно отображал новое значение из Knobs, эта проблема вызвана useState:
Из документов React:
Рабочее решение Storybook 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 🙂.