2

I have a React / TypeScript component. In Button.tsx:

type Props = { type: | "primary" | "secondary" | "tertiary" } const Button = React.FC<Props> = ({ type }) => { const color = (() => { switch (type) { case "primary": return 'red'; case "secondary": return 'blue'; case "tertiary": return 'green'; default: throw new Error("A backgroundColor condition was missed"); } })(); return( <button style={{ background: color }}>Im a button</button> ) } 

Which I can use in other components. In Page.tsx:

const Page = () => { return( <div> <h1>Heading</h1> <Button type="primary" /> </div> ) } 

In Storybook I need to use all of the type values. In Button.stories.js:

const types = [ "primary", "secondary", "tertiary", ]; export const AllButtons = () => { return( types.map(type=>{ <Button type={type} key={type} /> }) ) } 

Rather than having to repeat "primary", "secondary", "tertiary" is there a way I can export them from Button.tsx? That way if a new type is added the Storybook file will automatically have it.

I could use an enum in Button.tsx:

export enum Types { primary = "primary", secondary = "secondary", tertiary = "tertiary", } type Props = { type: Types; }; 

However then components that use Button cant just pass a string, you would have to import the enum every time you used Button, which isn't worth the trade off. In Page.tsx:

import { Type } from './Button' const Page = () => { return( <div> <h1>Heading</h1> <Button type={Type.primary} /> </div> ) } 
1

2 Answers 2

1
+200

In TypeScript you can get a type from a value (using typeof) but you can never get a value from a type. So if you want to eliminate the duplication, you need to use a value as your source of truth and derive the type from it.

For example, if you make the array of button types the source of truth, then you can use a const assertion (as const) to derive the type from it:

// Button.tsx export const BUTTON_TYPES = [ "primary", "secondary", "tertiary", ] as const; type Types = typeof BUTTON_TYPES[number]; type Props = { type: Types; } const Button: React.FC<Props> = ({ type }) => { // ... return( <button style={{ background: color }}>Im a button</button> ) } 

Then you can import BUTTON_TYPES in your story and iterate over it.

You could also make a mapping from button type to color and use that as your source of truth. This would let you eliminate the color function from your component:

const TYPE_TO_COLOR = { primary: 'red', secondary: 'blue', tertiary: 'green', } as const; type Types = keyof typeof TYPE_TO_COLOR; // type Types = "primary" | "secondary" | "tertiary" export const BUTTON_TYPES = Object.keys(TYPE_TO_COLOR); type Props = { type: Types; } const Button: React.FC<Props> = ({ type }) => { const color = TYPE_TO_COLOR[type]; if (!color) { throw new Error("A backgroundColor condition was missed"); } return ( <button style={{ background: color }}>Im a button</button> ); } 
Sign up to request clarification or add additional context in comments.

Comments

1

You can generate a declare an object and declare a type from it. This way, you'll be able to iterate through its keys and the type will be up to date on each change.

Button.tsx

import * as React from "react"; export const ButtonSkins = { primary: "primary", secondary: "secondary", tertiary: "tertiary" }; export type ButtonSkin = keyof typeof ButtonSkins; export type ButtonProps = { skin: ButtonSkin; }; export const Button: React.FC<ButtonProps> = ({ skin }) => ( <button className={skin}>{skin}</button> ); 

App.tsx (I put the loop here but you can use it in the storybook of course)

import * as React from "react"; import { render } from "react-dom"; import { Button, ButtonSkins, ButtonSkin } from "./Button"; const App = () => ( <div> {(Object.keys(ButtonSkins) as Array<ButtonSkin>).map(skin => { return <Button skin={skin} />; })} <h2>Start editing to see some magic happen {"\u2728"}</h2> </div> ); render(<App />, document.getElementById("root")); 

https://codesandbox.io/s/recursing-browser-qkgzb?file=/src/index.tsx

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.