Describe the bug
Passing a Context through a decorator doesn't work as expected.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
useContext should work and return the updated value from the context provider
Code snippets
MemeContext.js
import React from 'react';
export const MemeContext = React.createContext({name: 'undefined'});
PrimaryButton.stories.tsx
import React, {useContext} from 'react';
import {MemeContext} from "../../../context/MemeContext";
export default {
title: 'Common|Buttons.PrimaryButton',
decorators: [storyFn => {
return (<MemeContext.Provider value={{name: 'sofiageo'}}>{storyFn()}</MemeContext.Provider>)
}]
};
export const Default = ({...rest}) => {
const {name} = useContext(MemeContext);
return (
<div>
{name}
</div>
);
};
System:
Environment Info:
System:
OS: Linux 5.3 Arch Linux undefined
CPU: (4) x64 AMD FX(tm)-4300 Quad-Core Processor
Binaries:
Node: 12.11.1 - /usr/bin/node
npm: 6.11.3 - /usr/bin/npm
npmPackages:
@storybook/addon-a11y: ^5.2.4 => 5.2.4
@storybook/addon-actions: ^5.2.4 => 5.2.4
@storybook/addon-contexts: ^5.2.4 => 5.2.4
@storybook/addon-info: ^5.2.4 => 5.2.4
@storybook/addon-links: ^5.2.4 => 5.2.4
@storybook/addon-options: ^5.2.4 => 5.2.4
@storybook/addon-storysource: ^5.2.4 => 5.2.4
@storybook/addons: ^5.2.4 => 5.2.4
@storybook/react: ^5.2.4 => 5.2.4
Additional context
The proper functionality can be seen by using a Consumer instead of a hook:
export const Default = ({...rest}) => {
return (
<MemeContext.Consumer>
{context => context.name}
</MemeContext.Consumer>
);
};
If the context is defined on a TS file, storybook gives the following error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of `storyFn`.
Since the project is on typescript, this has been reproduced using both awesome-typescript-loader
and ts-loader
- without babel
I've installed babel-loader and now the TS error is gone, but the issue still persist. So it looks there are two issues, one is TS files without babel, and the other one is the context hook not updating properly.
@sofiageo What happens when you make this change?
decorators: [StoryFn => {
return (<MemeContext.Provider value={{name: 'sofiageo'}}><StoryFn /></MemeContext.Provider>)
}]
It works fine, was I using a wrong syntax?
The TS issue I think is caused by storybook/source-loader addon. When I disable it I get no errors.
The <StoryFn />
syntax compiles to React.createElement(), which is needed for hooks. Didn't occur to me until I read the issue in its entirety. Thanks for putting this together!!
One thing I've noticed about the <StoryFn />
solution is that adjusting values with the Knobs addon causes the entire story to remount, not re-render. This limits the usefulness (in my stories at least) because the knobs are often used to test animations and such 鈥撀爏o if the story remounts, there's no animation since the state is completely reinitialized.
A better approach I've found is:
const Story = ({ storyFn }) => storyFn();
const myDecorator = storyFn => <Provider><Story storyFn={storyFn} /></Provider>;
That way, storyFn
is called as a descendant of Provider, thus gets its context, and the story is not remounted when knobs change.
cc @tmeasday
Most helpful comment
The
<StoryFn />
syntax compiles to React.createElement(), which is needed for hooks. Didn't occur to me until I read the issue in its entirety. Thanks for putting this together!!