Storybook: Using knobs-select to choose between different React components

Created on 27 May 2020  路  11Comments  路  Source: storybookjs/storybook

I'm new to storybook and I'm testing the addon knobs. I have an <ErrorHandler /> component which displays an error in case there is an issue in the children component.

You can whether use the error by default or pass an <Error /> component as a prop, so it would be something like this:

<ErrorHandler customErrorComponent={CustomComponent}>
  <Children />
</ErroHandler>

I was trying to use select to achieve it:

const Loading = () => <h1>Loading...</h1>;
const loadingComponent: () => select('Loading Component', { NoComponent: null, Component: Loading() }, null)

I added a console.log and I'm receiving the object properly:

object {
  '$$typeof': Symbol(react.element),
  type: 'h1',
  key: null,
  ref: null,
  props: { children: 'Loading...' },
  _owner: null,
  _store: {}
}

But surprisingly I'm getting an error:

Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.
    in DisplayDataFromArray (at DisplayDataFromArray.stories.jsx:26)
    in StylesProvider (at config.js:8)
    in storyFn
    in ErrorBoundary
    at throwOnInvalidObjectType (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:95796:15)
    at reconcileChildFibers (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:96696:7)
    at reconcileChildren (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:99154:28)
    at updateFunctionComponent (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:99438:3)
    at beginWork (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:101004:16)
    at HTMLUnknownElement.callCallback (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:82549:14)
    at Object.invokeGuardedCallbackDev (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:82598:16)
    at invokeGuardedCallback (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:82653:31)
    at beginWork$1 (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:105595:7)
    at performUnitOfWork (http://localhost:6006/vendors~main.125fd0ca26f69bb91b0e.bundle.js:104546:12)

So my question is, is this a bug or knobs doesn't allow me to use React Components this way?

knobs react inactive question / support

Most helpful comment

Hey @winterlamon. Thanks for your comment and sorry for being this late. With the new addon-controls, it's working perfectly. I'm currently using v6.0.0-beta.21

My code looks like:

const LoadingComponent = () => <h1>Loading...</h1>;
const loadingComponentOptions = { WithComponent: LoadingComponent(), WithoutComponent: null };

export const Main = () => (
  <Component />
);

Main.argTypes = {
  loadingComponent: {
    control: {
      type: 'select',
      options: loadingComponentOptions,
    },
  },
};

The only thing that is not working, is the defaultValue param when you are passing a React component as an option. If you are using a string, number, etc. it does work.

All 11 comments

Hi gang, We鈥檝e just released addon-controls in 6.0-beta!

Controls are portable, auto-generated knobs that are intended to replace addon-knobs long term.

Please upgrade and try them out today. Thanks for your help and support getting this stable for release!

Hi gang, We鈥檝e just released addon-controls in 6.0-beta!

Controls are portable, auto-generated knobs that are intended to replace addon-knobs long term.

Please upgrade and try them out today. Thanks for your help and support getting this stable for release!

Hey @shilman, the addon controls is looking great, I'm loving it. I've upgraded react and all the storybook dependencies and it works perfectly, although I'm struggling to get to work a "control type select".

I'm trying with something like this:

Component.argTypes = {
  data: { control: { type: 'select', arrayWithData: ['data'], emptyArray: [], wrongDataType: 'data' } },
};

I've also tried adding the defaultValue but it's not working either.

I assume I'm doing it the wrong way. Are you guys planning to update the doc with more examples for different control types?

For anybody who is interested in Controls but don't know where to start, I've created a quick & dirty step-by-step walkthrough to go from a fresh CRA project to a working demo. Check it out:

=> Storybook Controls w/ CRA & TypeScript

There are also some "knobs to controls" migration docs in the Controls README:

=> How do I migrate from addon-knobs?

Thanks for your response @shilman. With the new example added to the doc:

export default {
  title: 'Widget',
  component: Widget,
  argTypes: {
    loadingState: {
      type: 'inline-radio',
      options: ['loading', 'error', 'ready'],
    },
  },
};

I better understood how to add a control type select, although instead of using an array for options, I've used an object so I can set a custom name:

  data: {
    control: {
      type: 'select',
      options: { WithData: ['data'], WithoutData: [], WithWrongData: 'data' },
    },
  },

It is working fine this way, the problem is that whenever I set a defaultValue, it properly sets the value to the default one, but the dropdown list doesn't show the selected option, instead, it shows a white background:

  data: {
    control: {
      type: 'select',
      options: { WithData: ['data'], WithoutData: [], WithWrongData: 'data' },
    },
    defaultValue: ['data'],
  },

This is what I get:
img
It is no big deal and it's not related to this issue, so I can create another ticket for this.

@davidbq quite possibly a bug. Please file an issue and I鈥檒l try to take a look in the next day or two

Hey, @davidbq. I haven't tested the waters in v6 with addon-controls yet (I'm currently using v5.3.17), but I ended up wanting to do something similar where I could use the select knob to switch between showing a button, link, or no CTA in a story (instead of having to make multiple stories). I ended up achieving it by creating a map of the components and using the select to choose the object key that corresponded to the component value.

const ctaOptions = {
  Button: <Button>Button CTA</Button>,
  Link: <Link href="#">Link CTA</Link>,
  None: null,
}

export const component = () =>
  <Component
    cta={ctaOptions[select('cta', ['Button', 'Link', 'None'], 'Button')]}
  />

Hey @winterlamon. Thanks for your comment and sorry for being this late. With the new addon-controls, it's working perfectly. I'm currently using v6.0.0-beta.21

My code looks like:

const LoadingComponent = () => <h1>Loading...</h1>;
const loadingComponentOptions = { WithComponent: LoadingComponent(), WithoutComponent: null };

export const Main = () => (
  <Component />
);

Main.argTypes = {
  loadingComponent: {
    control: {
      type: 'select',
      options: loadingComponentOptions,
    },
  },
};

The only thing that is not working, is the defaultValue param when you are passing a React component as an option. If you are using a string, number, etc. it does work.

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!

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

Thanks for the helpful code @davidbq ! Appreciate it a lot!

I have a similiar use case but get an error:

SvgAccessibleIcon(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.

I am using Storybook 6.0.19. Can you help out?

index.stories.mdx

import { Meta, Story, Preview, Props, Canvas, ArgsTable } from '@storybook/addon-docs/blocks';

import { Icon } from './';
import  { SvgPhoneSolid, SvgAccessibleIcon, Svg500px } from './iconMap.js'

<Meta 
  title="Components/Icon"
  component={Icon}
  argTypes={{
    name: {
      control: {
        type: 'select',
        options: { SvgPhoneSolid, SvgAccessibleIcon, Svg500px }
      }
    }
  }}  
/>

./iconMap.js

import { ReactComponent as Svg500px } from '../../../../../src/assets/icons/500px.svg';
import { ReactComponent as SvgAccessibleIcon } from '../../../../../src/assets/icons/accessible-icon.svg';
import { ReactComponent as SvgAccusoft } from '../../../../../src/assets/icons/accusoft.svg';
//...


export {
  Svg500px,
  SvgAccessibleIcon,
  SvgAccusoft,
  //...
}

console.log(SvgPhoneSolid)

茠 SvgPhoneSolid(props) {
  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"]("svg", _extends({
    viewBox: "0 0 32 32"
  }, props), _ref);
}

Hey, @davidbq. I haven't tested the waters in v6 with addon-controls yet (I'm currently using v5.3.17), but I ended up wanting to do something similar where I could use the select knob to switch between showing a button, link, or no CTA in a story (instead of having to make multiple stories). I ended up achieving it by creating a map of the components and using the select to choose the object key that corresponded to the component value.

const ctaOptions = {
  Button: <Button>Button CTA</Button>,
  Link: <Link href="#">Link CTA</Link>,
  None: null,
}

export const component = () =>
  <Component
    cta={ctaOptions[select('cta', ['Button', 'Link', 'None'], 'Button')]}
  />

As an improvement:

cta={ctaOptions[select('cta', Object.keys(ctaOptions), 'Button')]}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexanbj picture alexanbj  路  3Comments

miljan-aleksic picture miljan-aleksic  路  3Comments

rpersaud picture rpersaud  路  3Comments

purplecones picture purplecones  路  3Comments

Jonovono picture Jonovono  路  3Comments