Storybook: How to use context?

Created on 7 Apr 2016  Â·  27Comments  Â·  Source: storybookjs/storybook

Currently many UI libraries like material-ui depend on the context for theming. I wasn't able to figure out a way to setup this. Maybe I should have a global container and use it to wrap all my stories? It doesn't feel 100% clean, but I wanted to check if this if the correct approach.

Thanks for react-storybook, this is amazing! We've wanted this for so long in our team, it's currently changing the way we work with new projects. :)

discussion feature request

Most helpful comment

@michaltakac No need for the materialize function. What you're doing is easily done via Storybook's built in addDecorator method:

import React from 'react';
import { action, storiesOf } from '@kadira/storybook';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

import StarRating from '../common-ui/components/StarRating';

storiesOf('common.StarRating', module)
  .addDecorator(story => (
    <MuiThemeProvider muiTheme={getMuiTheme()}>
      {story()}
    </MuiThemeProvider>
  ))
  .add('summary mode', _ => (
    renderStarRating()
  ))
  .add('details mode', _ => (
    renderStarRating({ mode: 'details' })
  ))
  .add('rate mode', _ => (
    renderStarRating({ mode: 'rate' })
  ));

function renderStarRating(props) {
  const ratings = [
    { name: 'Bob G', rating: 5 },
    { name: 'Ali G', rating: 4 },
    { name: 'Frankenstein', rating: 1 },
    { name: 'Santa Claus', rating: 3 },
    { name: 'Another Person', rating: 4 },
    { name: 'Some Gal', rating: 5 },
    { name: 'Some Critical Dude', rating: 1 },
  ];

  return <div style={rootStyle}>
    <StarRating ratings={ratings} { ...props } />
  </div>
}

const rootStyle = {
  alignItems: 'center',
  display: 'flex',
  justifyContent: 'center',
  marginTop: 200,
};

All 27 comments

Hi @davidpelaez we are doing this by setting context on each component. Not ideal, but in the context of a component library which we are building - it makes sense, having each component be self containing so they can be used in isolation.

import React from 'react'
import ThemeManager from 'material-ui/lib/styles/theme-manager'
import myTheme from '../theme/my-theme' //path to your custom theme
import AppBar from 'material-ui/lib/app-bar';

const ExampleComponent = React.createClass({
  propTypes: {
    children: React.PropTypes.any.isRequired
  },

  childContextTypes: {
    muiTheme: React.PropTypes.object
  },

  getChildContext () {
    return {
      muiTheme: ThemeManager.getMuiTheme(myTheme)
    }
  },


  render () {
    return (
      <div>
        <AppBar title="Title" iconClassNameRight="muidocs-icon-navigation-expand-more" />
      </div>
    )
  }
})

export default ExampleComponent

@davidpelaez, They way you do it the correct way to do it. But, I think we can introduce a new API to make these things a bit easier.

See:

import { storiesOf } from '@kadira/storybook';
import Context from '../context';
import MyComp from '../my_comp';

storiesOf('MyComp').
  .addDecorator(function(getStory) {
    return (<Context>getStory()</Context>)
  })
  .add('default view', () => (
    <MyComp>Something here</MyComp>
  ))

This is just a nice syntax but it will wrap each story with the context. But, I think that's the way it should be anyway.

The same is needed for material-ui. Currently I wrap every story with a <WithTheme>/<WithTheme> component. +1

@mcbain exactly I came to this because of material-ui. Thanks for pointing
out that using the wrapper was correct. It would be nice to either have
this is in the docs or have a dedicated helper for it. A slightly closer
integration to small details of React like this would be great. I've seen
many constants update in the last dates and you are doing an amazing job.
Whatever you choose will work great :)

David Peláez Tamayo
Designer & Entrepreneur

On Fri, Apr 15, 2016 at 1:47 AM, mcbain [email protected] wrote:

The same is needed for material-ui. Currently I wrap every story with a
/ component. +1

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/kadirahq/react-storybook/issues/76#issuecomment-210309813

@mnmtanish could you work on the addDecorator() API when you have some time. Make a comment if you are working on this.

If some else could do it before @mnmtanish that's great too :)

@arunoda just wrote a simple implementation here. Let me know if there are any changes.
It should work but I haven't tested it with a React demo yet. I'll comment here after testing in a while.

Could you not use something like https://github.com/mattzeunert/react-with-context?

I'm tring to use storybook with Material-ui. And the Dropdown menu in material-ui not response for clicking.
After reading above I have tried to use new addDecorator API, but import Context from '../context'; cause new Error:

...
ERROR in ./client/configs/context.js
Module not found: Error: Cannot resolve module 'meteor/reactive-dict' in /Users/walter/WebstormProjects/meteor13test/client/configs
 @ ./client/configs/context.js 25:20-51
...

here is content in context.js:

import * as Collections from '/lib/index';
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router-ssr';
import { ReactiveDict } from 'meteor/reactive-dict';
import { Tracker } from 'meteor/tracker';

export default function () {
    return {
        Meteor,
        FlowRouter,
        Collections,
        LocalState: new ReactiveDict(),
        Tracker,
    };
}

@wuzhuzhu

Storybook can't read meteor/xxx dependencies since they are some special deps managed by Meteor. Seems like you are using Mantra and you should not import context directly. That may fix your issue.

@wuzhuzhu

I had the same issue with SelectField, the answer is in the material-ui readme.md You need to load the react-tap-event-plugin when storybook loads.

Some components use react-tap-event-plugin to listen for touch events because onClick is not fast enough This dependency is temporary and will eventually go away. Until then, be sure to inject this plugin at the start of your app.

import injectTapEventPlugin from 'react-tap-event-plugin';

// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();

Thanks for both of your answers! It's working now.

Is it possible get formsy-react to work with storybook / meteor-mantra ? All fields just disappears in storybook.

@VirtueMe Where did you wind up putting the injectTapEventPlugin() call? I keep getting errors, because it can only be invoked once. I'm not sure what the best place for it is.

I added it to the .storybook/config.js file.

Was trying to use Material UI with storybook today and came up with this solution:

/stories/materialize.js:

import React from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import defaultTheme from '../../../common/app/defaultTheme';


export default function materialize(Component, props) {
  return (
    <MuiThemeProvider muiTheme={defaultTheme}>
      <Component {...props} />
    </MuiThemeProvider>
  );
}

stories/header.js:

import React from 'react';
import Header from '../Header';
import materialize from './materialize';
import { storiesOf, action } from '@kadira/storybook';

storiesOf('Header', module)
  .add('default button view', () => {
    const props = {
      label: "Button"
    }
    return materialize(Header, props);
  })
  .add('primary button view', () => {
    const props = {
      label: "Button",
      primary: true
    }
    return materialize(Header, props);
  });

@michaltakac No need for the materialize function. What you're doing is easily done via Storybook's built in addDecorator method:

import React from 'react';
import { action, storiesOf } from '@kadira/storybook';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

import StarRating from '../common-ui/components/StarRating';

storiesOf('common.StarRating', module)
  .addDecorator(story => (
    <MuiThemeProvider muiTheme={getMuiTheme()}>
      {story()}
    </MuiThemeProvider>
  ))
  .add('summary mode', _ => (
    renderStarRating()
  ))
  .add('details mode', _ => (
    renderStarRating({ mode: 'details' })
  ))
  .add('rate mode', _ => (
    renderStarRating({ mode: 'rate' })
  ));

function renderStarRating(props) {
  const ratings = [
    { name: 'Bob G', rating: 5 },
    { name: 'Ali G', rating: 4 },
    { name: 'Frankenstein', rating: 1 },
    { name: 'Santa Claus', rating: 3 },
    { name: 'Another Person', rating: 4 },
    { name: 'Some Gal', rating: 5 },
    { name: 'Some Critical Dude', rating: 1 },
  ];

  return <div style={rootStyle}>
    <StarRating ratings={ratings} { ...props } />
  </div>
}

const rootStyle = {
  alignItems: 'center',
  display: 'flex',
  justifyContent: 'center',
  marginTop: 200,
};

Thank you for example code, helped!

Shouldn't this be closed now that addDecorator exists?

@vladfr I think yes.

Hi!

Thank you for finding such an elegant solution to this annoying problem!

However, can the MUI support be generalized in a way that I can add the decorator only once, in the global config, instead of each individual story?

For reference, my .storybook/config.js looks like this:

import { configure } from '@kadira/storybook';
import { setStubbingMode } from 'react-komposer';

// See: https://github.com/kadirahq/react-komposer#stubbing
setStubbingMode(true);

function loadStories() {
  require('../client/modules/core/components/.stories');
  require('../client/modules/comments/components/.stories');
}

configure(loadStories, module);

@Domiii You can add decorator both locally and globally:

import { addDecorator } from '@kadira/storybook';
import { muiTheme } from 'storybook-addon-material-ui';
// You can add decorator globally:
addDecorator(muiTheme());

@UsulPro Does this work for you with the latest version?

When visiting the storybook frontend, I get:

ERROR in ./~/react-material-color-picker/dist/ic_done_black_64dp_1x.png
Module parse failed: D:\code\meteor\mantra-sample-blog-app\node_modules\react-material-color-picker\dist\ic_done_black_64dp_1x.png Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected character '�' (1:0)
    at Parser.pp$4.raise (D:\code\meteor\mantra-sample-blog-app\node_modules\acorn\dist\acorn.js:2221:15)
    at Parser.pp$7.getTokenFromCode (D:\code\meteor\mantra-sample-blog-app\node_modules\acorn\dist\acorn.js:2756:10)
    at Parser.pp$7.readToken (D:\code\meteor\mantra-sample-blog-app\node_modules\acorn\dist\acorn.js:2477:17)
    at Parser.pp$7.nextToken (D:\code\meteor\mantra-sample-blog-app\node_modules\acorn\dist\acorn.js:2468:15)
    at Parser.parse (D:\code\meteor\mantra-sample-blog-app\node_modules\acorn\dist\acorn.js:515:10)
    at Object.parse (D:\code\meteor\mantra-sample-blog-app\node_modules\acorn\dist\acorn.js:3098:39)
    at Parser.parse (D:\code\meteor\mantra-sample-blog-app\node_modules\webpack\lib\Parser.js:902:15)
    at DependenciesBlock.<anonymous> (D:\code\meteor\mantra-sample-blog-app\node_modules\webpack\lib\NormalModule.js:104:16)
    at DependenciesBlock.onModuleBuild (D:\code\meteor\mantra-sample-blog-app\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
    at nextLoader (D:\code\meteor\mantra-sample-blog-app\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
 @ ./~/react-material-color-picker/dist/MaterialColorPicker.js 39:29-67

@Domiii your issue is your webpack config can't understand png files.
Our default setup should support it.
But if you are using a custom webpack loader, try to use a proper loader for png files.

I did an horrible:

const story = (...args) => ({
    add(name, fn) {
        storiesOf(...args).add(name, () => <Wrapper>{fn()}</Wrapper>);
        return this;
    },
});

same usage than before:

configure(() => {
    story('StatusChip', module)
        .add('with status', () => <StatusChip status="Rejected" />)
        .add('with progress', () => <StatusChip progress={72} />);
})

It would be great to allow to configure storiesOf, I'll see if I can make a PR later, because passing a wrapper, or an extra function every time is too much (@michaltakac)

@caub this should do effectively the same:

import { addDecorator } from '@storybook/react'

addDecorator(fn => <Wrapper>{fn()}</Wrapper>)

See https://storybook.js.org/basics/writing-stories/#using-decorators

I'm using "useContext(user)" in my component and use that variable to populate the component. How would I be able to get that data into the storybook? I can't useContext without a functional component, so I don't know how to access that data in the storybook? I looked everywhere to see an example, but couldn't find it.

@AlexVotry

I can't useContext without a functional component

Yeah you definitely need one:

const Example = () => {
  const user = useContext(UserContext)
  return <MyComponent user={user} />
}

storiesOf('MyComponent', module).add('default', () => <Example />)

Or, maybe even better, useContext directly in MyComponent

Was this page helpful?
0 / 5 - 0 ratings