useCounter, useSlider, usePicker
You can write hooks using other hooks. Consider these examples -

A custom useCounter hook shows us how we can write small child components which send their state upwards to the parent component -
const useCounter = (init = 0) => {
const [value, setValue] = useState(init)
return [
value,
<div>
<button onClick={_ => setValue(value - 1)}>-</button>
{value}
<button onClick={_ => setValue(value + 1)}>+</button>
</div>
]
}
In MyApp, when useCounter is called, we get the stateful value and the stateful counter component -
const initialOrder =
{ muffin: 0, cakes: 0, pies: 0, ... }
const MyApp = ({ order = initialOrder }) => {
const [muffins, muffinCounter] = useCounter(order.muffins)
const [cakes, cakeCounter] = useCounter(order.cakes)
const [pies, piesCounter] = useCounter(order.pies)
// ...
return <ul>
<li>muffins: {muffinCounter}</li>
<li>cakes: {cakeCounter}</li>
<li>pies: {piesCounter}</li>
// ...
</ul>
}
We can make similar components like this useSlider custom hook -
const useSlider = ({ min = 0, max = 0, init = 0 }) => {
const [value, setValue] = useState(init)
return [
value,
<input
type="range"
min={min}
max={max}
value={value}
onChange={e => setValue(e.target.value)}
/>
]
}
And custom hooks made out of custom hooks, usePicker -
const usePicker = ({ values = [], init = 0 }) =>
useSlider({ min: 0, max: values.length - 1, init })
const MyApp = ({ order = initialOrder }) => {
const [muffins, muffinCounter] = useCounter(order.muffins)
const [cakes, cakeCounter] = useCounter(order.cakes)
const [pies, piesCounter] = useCounter(order.pies)
const speeds = ["slow", "medium", "fast", "same-day"]
const [deliverySpeed, speedPicker] = usePicker({ values: speeds, init: order.deliverySpeed })
// ...
return <ul>
<li>muffins: {muffinCounter}</li>
<li>cakes: {cakeCounter}</li>
<li>pies: {piesCounter}</li>
<li>speed: {speedPicker} {speeds[deliverySpeed]}</li>
// ...
</ul>
}
Run the code below to see useCounter, useSlider, and usePicker in action -
const { useState, useEffect } = React
const useCounter = (init = 0) => {
const [value, setValue] = useState(init)
return [
value,
<div>
<button onClick={_ => setValue(value - 1)}>-</button>
{value}
<button onClick={_ => setValue(value + 1)}>+</button>
</div>
]
}
const useSlider = ({ min = 0, max = 0, init = 0 }) => {
const [value, setValue] = useState(init)
return [
value,
<input type="range" min={min} max={max} value={value} onChange={e => setValue(e.target.value)} />
]
}
const usePicker = ({ values = [], init = 0 }) =>
useSlider({ min: 0, max: values.length - 1, init })
const initialOrder =
{ muffin: 0, cakes: 0, pies: 0, deliverySpeed: 3 }
const MyApp = ({ order = initialOrder }) => {
const [muffins, muffinCounter] = useCounter(order.muffins)
const [cakes, cakeCounter] = useCounter(order.cakes)
const [pies, piesCounter] = useCounter(order.pies)
const speeds = ["slow", "medium", "fast", "same-day"]
const [deliverySpeed, speedPicker] = usePicker({ values: speeds, init: order.deliverySpeed })
const onSubmit = e =>
alert("submitted: " + JSON.stringify({ muffins, cakes, pies, deliverySpeed }))
return <ul>
<li>muffins: {muffinCounter}</li>
<li>cakes: {cakeCounter}</li>
<li>pies: {piesCounter}</li>
<li>speed: {speedPicker} {speeds[deliverySpeed]} </li>
<li>{JSON.stringify({ muffins, cakes, pies, deliverySpeed })}</li>
<li><button onClick={onSubmit}>submit</button></li>
</ul>
}
ReactDOM.render(<MyApp />, document.body)
body { margin: 0 auto; font-family: monospace; }
ul { padding: 0; }
li { list-style-type: none; background-color: ghostwhite; padding: 0.25rem; margin-bottom: 0.25rem; }
li div { display: inline-block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
example: useAsync, useJson
A custom useAsync hook allows us to declaratively interact with an asynchronous resources -
function useAsync(func, [_0, _1, _2, _3, _4]) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [result, setResult] = useState(null)
useEffect(_ => {
setLoading(true)
Promise.resolve(func(_0, _1, _2, _3, _4))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, [func, _0, _1, _2, _3, _4])
return {loading, error, result}
}
In a typical example we just want to do a fetch request and parse some JSON. Another custom hook useJson built using our first custom hook, useAsync -
const fetchJson = (url = "") =>
fetch(url).then(r => r.json())
const useJson = (url = "") =>
useAsync(fetchJson, [url])
Using useJson in MyComponent looks like this -
const MyComponent = ({ url = "" }) => {
const { loading, error, result } = useJson(url) // ✨
if (loading)
return <pre>loading...</pre>
if (error)
return <pre style={{color: "tomato"}}>error: {error.message}</pre>
return <pre>result: {result}</pre>
}
Run the code demo below to see useJson in action -

const { useState, useEffect } = React
// fake fetch slows response down so we can see loading
const _fetch = (url = "") =>
fetch(url).then(x => new Promise(r => setTimeout(r, 2000, x)))
function useAsync(func, [_0, _1, _2, _3, _4]) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [result, setResult] = useState(null)
useEffect(_ => {
setLoading(true)
Promise.resolve(func(_0, _1, _2, _3, _4))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, [func, _0, _1, _2, _3, _4])
return {loading, error, result}
}
const fetchJson = (url = "") =>
_fetch(url).then(r => r.json())
const useJson = (url = "") =>
useAsync(fetchJson, [url])
const MyComponent = ({ url = "" }) => {
const { loading, error, result } = useJson(url)
if (loading)
return <pre>loading...</pre>
if (error)
return <pre style={{color: "tomato"}}>error: {error.message}</pre>
return <pre>result: {JSON.stringify(result, null, 2)}</pre>
}
const MyApp = () =>
<main>
ex 1 (success):
<MyComponent url="https://httpbin.org/get?foo=bar" />
ex 2 (error):
<MyComponent url="https://httpbin.org/status/500" />
</main>
ReactDOM.render(<MyApp />, document.body)
pre { background: ghostwhite; padding: 1rem; white-space: pre-wrap; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
Enjoy your cupcakes!