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
- Part 1, Element
- Part 2, useEffect
- Part 3, useState
- Part 4, Hook
- Part 5, Custom Hook <- YOU ARE HERE
- Part 6, useContext