While the logic of useReducer might seem complicated, it’s a hook that you can
rewrite with only a few lines of code. Are you able to write your custom
implementation of it?
const useMyReducer = (reducer, initialState) => {
return [initialState, () => {}]
}
const initialState = {
loading: false,
error: false,
planet: null,
}
const reducer = (state, action) => {
switch (action.type) {
case 'load':
return { ...state, loading: true, error: false }
case 'success':
return { ...state, loading: false, planet: action.planet }
case 'error':
return { ...state, loading: false, error: true }
}
}
const fetchStart = () => ({ type: 'load' })
const fetchSuccess = (planet) => ({ type: 'success', planet })
const fetchError = () => ({ type: 'error' })
const Planet = () => {
const [state, dispatch] = useMyReducer(reducer, initialState)
const { loading, error, planet } = state
useEffect(() => {
let active = true
dispatch(fetchStart())
fetch('https://swapi.dev/api/planets/1/')
.then((res) => res.json())
.then((planet) => {
if (active) {
dispatch(fetchSuccess(planet))
}
})
.catch((error) => {
if (active) {
console.error(error)
dispatch(fetchError())
}
})
return () => (active = false)
}, [])
if (loading) {
return <p>Loading…</p>
} else if (error) {
return <p>An error occurred.</p>
} else if (planet) {
return <p>Planet: {planet.name}</p>
} else {
return null
}
}
render(Planet)
Click here to see the solution!
The custom hook uses useState to store the reducer’s state. To update it, it
returns a dispatch function. This functions:
- computes the new state using the action and the reducer:
reducer(state, action)), - updates the state using
setState.
const useMyReducer = (reducer, initialState) => {
const [state, setState] = useState(initialState)
const dispatch = (action) => setState(reducer(state, action))
return [state, dispatch]
}
Next steps
In the next lesson, we’ll see some example of custom hooks to use the APIs
provided by the browser.