New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cannot use React hooks directly inside a story #5721
Comments
Not sure this is a bug. I believe, and I could be wrong, the second argument of Example
|
It shouldn't really matter AFAIK, but it's always worth trying. Also, @sargant what throws the error? Storybook or the type system? |
@Keraito No I'm pretty sure the error is correct. It's because we're calling the function out of the context of react, aka storybook will call the function like so: const element = storyFn(); not const element = <StoryFn /> Quite possibly if we'd initiate it like that it might work. Right now @gabefromutah's advice is sound. Here's the actual line of code: If someone wants to experiment to make this work, that's the place to start, I think. |
@sargant does @gabefromutah's suggestion work for you? |
@ndelangen @gabefromutah's suggestion is the same as my initial "this story works" example I believe? In the case that this is not a bug, it might still be a useful enhancement to avoid having to use third-party plugins for state. |
@ndelangen not entirely sure how that will interact with other certain addons, especially |
We've experienced the same issue, and this is a simple hack we do to workaround it. stories.add('this story fails', () => React.createElement(() => {
const [state, setState] = React.useState(5)
return (
<button onClick={() => setState(state + 1)}>
{state}
</button>
)
})) I agree it would be much better for it to be natively supported without hacks. If the maintainers agree too then maybe I can find some time to come up with a PR 🙂. |
@kevin940726 that would be awesome 👍 |
Adding the following to my .storybook/config.js worked for me
|
I've implemented something similar, but I think that breaks a bunch of addons, specifically addon-info for outputting prop documentation. I've decided using hooks is more important than that info (as i generate that separately anyways) but I doubt that will apply to all of storybook in general. No idea how you would handle a hooks like api outside of react. |
I tried to implement it in the source code, but I'm having a hard time to make it work with storyshot-addon. I think this would be a breaking change as it would generate a new parent node in every snapshots. As we are not in control of the renderer the user chooses, we cannot dive in one level deep. I think we might have to think of other alternatives, like we could document the solution and maybe provide some helper API for the users to opt-in. |
@emjaksa Could you provide a snippet of this please? |
This was my workaround to this problem: 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 }); As I mentioned is still a workaround, but it does work and don't break the |
My info addon doesn't break provided I added the Story decorator last. 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) |
Yet another workaround: I have a utility component called export const UseState = ({ render, initialValue }) => {
const [ variable, setVariable ] = useState(initialValue)
return render(variable, setVariable)
} And I use it in the stories like this: .add('use state example', () => (
<UseState
initialValue={0}
render={(counter, setCounter) => (
<button onClick={() => setCounter(counter + 1)} >Clicked {counter} times</button>
)}
/>
) But I like @kevin940726 's workaround the best |
I'm unable to get the story to re-render on state change. Force re-rendering doesn't work either. |
@artyomtrityak You can override the |
@shilman will it also include source of |
@artyomtrityak I'll see what I can do 😆 |
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks! |
@shilman thank you! |
@shiranZe are you using a recent 5.2-beta? |
@shilman yes... my code on stories/components/Menu/index.js: const component = () => {
export default [readme, component]; and in the stories/components/index.js: |
@shiranZe i'd guess it's an issue with |
Thank you @shilman for your reply. I don't know if that's the problem . I tried without |
I still have the issue with |
@shiranZe our netlify deploy is turned off (cc @ndelangen) but you can check out the repo on the |
@sourcesoft I believe that the knobs-related "preview hooks" issue (cc @Hypnosphi) has nothing to do with this issue -- the only thing they have in common is the concept of hooks. |
@shilman Thanks, I think you're right. Btw I figured out how to fix it by trial and error, changed the custom hook:
to
Basically just removed the use of |
In your above example, after removing the |
it sounds like there's some issue with using hooks with knobs. If anybody can provide a simple repro, I'd be happy to take a look at it |
@shilman Have you tried the example I posted earlier? Here is the snippet:
Its using the old API, but should be easy to transform to the latest API for testing. You could find the expected behaviour from the original post. |
@zhenwenc FYI the code works, but breaks usage of docs rendered by |
Workaround seems a bit brittle and conflicting with other libraries. As an alternative, does someone have experience using?: https://github.com/Sambego/storybook-state |
For those here googling the error message: I got this error when a changing a class component to a functional component with hooks and |
Just answering @orpheus's request for a snippet
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 />); Result (complete 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 />); I'm not sure if this is the best way to get react hooks working inside storybook, but wrapping storybook in its own component Before doing this, whenever I updated a knob (ie: a boolean), it worked on first render, but then stopped rendering afterward. Above solution fixed that. Btw, I'm not sure it's good practice to use react hooks in storybook, just mock everything if possible, it's probably better this way. |
====================================== Do not use arrow function to create functional components.
Or
If you have problems with "ref" (probably in loops), the solution is to use forwardRef():
|
how can this be achieved in MDX? |
I was having this exact same issue. The fix is to capitalize the named story export. import React from 'react'; import Foo from './Foo'; export default { title: 'Foo'; }; export const Basic = () => <Foo /> The docs say capitalization is recommended but it's necessary if you want that warning to go away. |
In my case, the problem was that I forgot to import the hook I was using. |
@emjaksa Thanx! Tested and working fine on Storybook v5.3. I had the same issue, my functional react component has Working solution Storybook v5.3: Update 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 And also update 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;
},
};
|
❓ SomeComponent.decorators = [(Story) => <Story />] |
@tmikeschu it's because the |
@shilman thank you! |
Also watch out for IDE autoimport feature, instead of importing
VSC imported it from:
|
I created a simple class to work with to fix this: import React from 'react';
import { storiesOf as storiesOfRN } from '@storybook/react-native';
export class Stories {
storyName: string;
stories: any;
constructor(storyName: string) {
this.stories = storiesOfRN(storyName, module);
}
add(name: string, fn: any): this {
const Fn = React.memo(fn);
this.stories.add(name, () => <Fn />);
return this;
}
}
export function storiesOf(storyName, b): Stories {
return new Stories(storyName);
} and use: import React from 'react';
import { storiesOf } from '../../../Stories';
storiesOf('Component', module)
.add('default', () => {
const [state, setOpenModal] = useState(true);
return null;
}); |
@albertoammar works well... Here's a more typed version of your solution as import React from "react";
import { storiesOf as storiesOfRN } from "@storybook/react-native";
export class Stories {
stories: ReturnType<typeof storiesOfRN>;
constructor(storyName: string, b: typeof module) {
this.stories = storiesOfRN(storyName, b);
}
add(name: string, fn: React.FC): this {
const Fn = React.memo(fn);
this.stories.add(name, () => <Fn />);
return this;
}
}
export function storiesOf(storyName: string, b: typeof module): Stories {
return new Stories(storyName, b);
} This has a major flaw though and that is the "knobs" no longer work. I am continuing on https://stackoverflow.com/questions/65012604/storybook-react-hook since this thread is getting too long. and the topic is clsoed anyway. |
Describe the bug
Cannot use hooks directly in a story, fails with
Hooks can only be called inside the body of a function component
.To Reproduce
Example code:
Expected behavior
First story works OK, second story fails on initial render
Versions
@storybook/react@4.1.13
react@16.8.2
react-dom@16.8.2
The text was updated successfully, but these errors were encountered: