Understanding Hooks Part 5 — Custom Hook

Fang Jin
3 min readMay 12, 2021

What’s behind the legendary Hooks? From time to time, I wonder.

Starting Part 4 the generic feature of a Hook is revealed. In this Part 5 we’d like to introduce a custom hook useAync to demonstrate how useState and useEffect can be served as the building blocks.

Say we want to fetch a call fn, wait for the data while loading without blocking the render.

/* Implementation */
const useAsync = (fn) => {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
fn().then(res => {
setData(res)
setLoading(false)
return res
})
}, [fn])
return [data, loading]
}
/* Usage */
const loadApi = () => {
return fetch().then(res => res.json())
}
const Component = () => {
const [data, loading] = useAsync(loadApi)
return (
<>
Welcome, {loading && '...'}
{data && 'you are here.'}
</>
)
}

The first time we render this piece, it’ll set initial value of data thanks to useState. Then we call useEffect to fetch api. While loading, "Welcome" is displayed. And after the api is fetched successfully, data is set again thus "You are here" is displayed. loading is flipped between true and false throughout the fetch.

Arguments

As you can see a custom hook can be used right away. What you have to be mindful is the input arguments. To remind us, useState ignores them after first update, useEffect takes them for all updates.

const Component = ({ choice }) => {
const [data, loading] = useAsync(choice ? loadApi1 : loadApi2)
return (
<>
Welcome,
{loading && '...'}
</>
)
}

If the api depends on the choice you make, either from the prop or another state variable. In the useAsync implementation, we set [fn] as the useEffect dependencies. Which means if the api switches, the registered effect will run again with the new api. Also the loading will be triggered once again to true and false.

You can see, the custom hook’s arguments can be used for once or for all times. This is flexible, good or bad, just keep in mind.

Component

A hook, more often, is used to extend the functionality of component in generic way. You will find quite a few custom hooks inside Table , Form related libraries. In our case, let’s wrap useAsync into a Async component for reuse.

const Async = ({ fn, children }) => {
const [data, loading] = useAsync(fn)
if (loading) return 'loading ...'
return React.cloneElement(children, { data })
}
const Component = () => {
return (
Welcome,
<Async fn={loadApi}>
<ChildComponent />
</Async>
)
}
const ChildComponent = ({ data }) => <></>

The children component is not rendered until data is resolved and provided properly. If you haven’t noticed yet, the hook, 99% of the time, is designed as a data layer.

The above useAsync code is only for study purpose. For production, please use something more bullet proof and versatile, ex. useSWR or useFetch.

TL;DR

The idea of custom hook is pretty simple, you have the atomic functionality provided by useState and useEffect. By putting them together you'll have more specific (or bigger) functionality.

Index

--

--

Fang Jin
Fang Jin

Written by Fang Jin

Front-end Engineer, book author of “Designing React Hooks the Right Way” and "Think in Recursion"

No responses yet