<style> .reveal pre { box-shadow: none; } .reveal pre code { padding: 1.25rem; border-radius: 0.125rem; } </style> # Lessons from building a component library --- **Give as much control as possible to the consumer** --- ## No booleans allowed --- Boolean props can conflict ```jsx <Button primary secondary>Click me</Button> ``` ![](https://i.imgur.com/6bwgIwa.gif) --- Enums are your friend ```jsx <Button variant="primary">Click me</Button> ``` --- Or even separate components ```jsx <PrimaryButton>Click me</PrimaryButton> ``` --- ## Naming things is hard (so don't) --- Name as few props as possible --- ```jsx function Button({ label, a11yLabel, handleClick }) { return ( <button onClick={handleClick} aria-label={a11yLabel}> {label} </button> ) } ``` ![](https://i.imgur.com/ybO1yCF.jpg) --- This component does nothing but rename props ```jsx <Button label="Click" a11yLabel="Add to cart" handleClick={addToCart} > ``` --- ```jsx function Button(props) { return <button {...props} /> } ``` ![](https://i.imgur.com/hBWLelk.jpg) --- Stick as closely to HTML as you can (it's had 26 years to get it right) ```jsx <Button aria-label="Add to cart" onClick={addToCart}> Click </Button> ``` --- Keep your prop names consistent across components No: ```jsx <Input onChange={...}> <Toggle onToggle={...}> ``` Yes: ```jsx <Input onChange={...}> <Toggle onChange={...}> ``` Consumers shouldn't have to keep checking the docs --- ## Give up control Consumers will expect to be able to control values ```jsx <Spinbutton value={valueFromReduxStore} onChange={setValueInReduxStore} > ``` --- ## Just use children --- Composition is the best part of React --- ```jsx <Header logo={<Logo>}> ``` How do we add a nav? --- Give the user control with composition ```jsx <Header> <Logo> <Nav marginLeft="auto"> </Header> ``` --- What if you need styling control? --- ## Compound components --- ```jsx import { Nav, Item } from "./Nav"; <Nav> <Item>About</Item> </Nav> ``` --- Separate imports can be annoying or impossible --- ```jsx import { Nav } from "@ticketmaster/mosaic"; <Nav> <Nav.Item>About</Nav.Item> </Nav> ``` --- ```jsx const Nav = styled.nav`...` const Item = styled.a`...` Nav.Item = Item; export default Nav; ``` --- ## Sharing state --- ![](https://i.imgur.com/L6OptFk.png) --- ```jsx <Spinbutton> <Spinbutton.Minus> <Spinbutton.Value> <Spinbutton.Plus> </Spinbutton> ``` `Minus` and `Plus` need click handlers and value needs the current value. --- React context lets us bypass the parent/child prop relationship --- ```jsx const SpinContext = React.createContext(); function Spinbutton() { const [value, setValue] = React.useState(0); return ( <SpinContext.Provider value={{ value, setValue }}> {children} </SpinContext.Provider> ) } ``` --- ```jsx function Plus() { const { value, setValue } = React.useContext(SpinContext); return ( <Button onClick={() => setValue(value + 1)}>+</Button> ) } ``` --- We can put whatever we want between the parent and child ```jsx <Spinbutton> <Flex flexDirection="column"> <Spinbutton.Minus> <strong><Spinbutton.Value></strong> <Spinbutton.Plus> </Flex> </Spinbutton> ``` --- ## The apropalypse --- ![](https://i.imgur.com/M6yYG3K.png) --- ![](https://i.imgur.com/HBhQZjx.png) --- ```jsx <Select options={["apple", "banana", "orange"]}> ``` --- > "We want to just display an icon instead of the label" > A designer, probably --- ```jsx <Select options={[ { name: "apple", icon: "🍎" }, { name: "banana", icon: "🍌" }, { name: "orange", icon: "🍊" } ]}> ``` --- > T̵̢̛͔̯͖͓̘̲̖͕͖̗̭͙͔́̀̑̅̀̊̎̋̉̊̈́͊͒h̴͓͓͈̙͔͇̺̩̹̰̦͈̙͐̆̀̈̽̆͆̇̃̕͠e̴̞̻̥̖̋ ̷̨̛̤͔͔̬͍̺͎̟̈́̏̐͗̈̍̑͌̆͋H̸̨̢̬̤͓̥̘͙̋̉̈́͗̓̈͑̒̇O̴̢̧̼̰̗͙̻͔͉̝͚͎̺̒͋͂̒͑̍̅͂̕͜S̴̛̛̟͚͛̈́͐͂̊̐̿t̶͔̗͍̭̣̗͈͍̪̱̽͑̉͆͆̈́̆̊́̃͜͠ only accepts FRUITCODE integers as values --- ```jsx <Select options={[ { name: "apple", icon: "🍎", value: 9384712 }, { name: "banana", icon: "🍌", value: 3938454 }, { name: "orange", icon: "🍊", value: 9034884 } ]}> ``` --- > Sometimes options have to be disabled --- ```jsx <Select options={[ { name: "apple", icon: "🍎", value: 938 }, { name: "banana", icon: "🍌", value: 393, disabled: true }, { name: "orange", icon: "🍊", value: 903 } ]}> ``` --- We're recreating our markup structure with objects --- HTML is composable by default, so copy it ```jsx <Select> <Option value={938} aria-label="apple">🍎</Option> <Option value={393} aria-label="banana" disabled>🍌</Option> <Option value={903} aria-label="apple">🍊</Option> </Select> ``` --- A composeable API is also more flexible ```jsx function Select({ children, options }) { return ( <Select}> {children || options.map(o => ( <Option value={o.value} aria-label={o.name}> {o.icon} </Option> ))} </Select> ) } ``` Now we can support both ways --- ## Resources - [Just use children](https://sid.st/post/just-use-children/) - [Compound.Components](https://sid.st/post/compound-components/) - [Compound components with React hooks](https://kentcdodds.com/blog/compound-components-with-react-hooks) - [Advanced React Patterns (videos)](https://courses.totalreact.com/p/advanced-react-free) --- **Thanks**
{"metaMigratedAt":"2023-06-14T22:52:44.443Z","metaMigratedFrom":"YAML","title":"Lessons from building a component library","breaks":true,"slideOptions":"{\"theme\":\"white\"}","contributors":"[{\"id\":\"95766997-b355-49e6-8c38-077c6a7ebb3b\",\"add\":7226,\"del\":3558}]"}
    440 views