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

---
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>
)
}
```

---
This component does nothing but rename props
```jsx
<Button
label="Click"
a11yLabel="Add to cart"
handleClick={addToCart}
>
```
---
```jsx
function Button(props) {
return <button {...props} />
}
```

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

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

---

---
```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}]"}