Storybook: React Context not working as expected

Created on 15 Oct 2019  路  7Comments  路  Source: storybookjs/storybook

Describe the bug
Passing a Context through a decorator doesn't work as expected.

To Reproduce
Steps to reproduce the behavior:

  1. Create a React Context on a TS or a JS file.
  2. Import that Context and pass it through a decorator, with an updated value.
  3. Try to use the context object via hooks (useContext)
  4. On TS files it will give an error, on JS files it will not get the updated value from the decorator.

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

react has workaround question / support

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!!

All 7 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xogeny picture xogeny  路  3Comments

tirli picture tirli  路  3Comments

shilman picture shilman  路  3Comments

zvictor picture zvictor  路  3Comments

purplecones picture purplecones  路  3Comments