What are Hooks?
Hooks are a feature introduced in React version 16.8 that allow developers to use state and other React features in functional components without the need for class components. Hooks are functions that allow you to use state, lifecycle methods, and other React features inside functional components.
There are several built-in hooks in React, including:
useState: allows you to add state to a component.
useEffect: allows you to perform side effects in a component (such as fetching data from an API and only run when value in dependency array change).
useLayoutEffect: similar to useEffect but runs synchronously after all DOM mutations.
useContext: allows you to access context values in a component.
useReducer: an alternative to useState that allows for more complex state management.
useRef: allows you to reference DOM elements or values that persist between renders.
useMemo: allows you to memoize a value (i.e. only recalculate when value in dependency array change).
useCallback: allows you to memoize a function (i.e. only recreate when value in dependency array change).
What is a custom hook?
React custom hooks are reusable functions that encapsulate and share logic between components. They allow developers to abstract away complex logic and create more modular, composable code. Custom hooks are created using the use
prefix and follow the rules of hooks in React, meaning they can only be called at the top level of a component or other hook.
As a React developer, it’s important to learn the process of creating custom hooks to solve problems or add missing features within your own React projects. You can create custom hooks for various purposes like fetching, posting data, managing local storage, listening to screen size (breakpoints) change for css in js.
To create a custom hook, it’s important to remember the following two rules:
Custom Hooks should always be named with the prefix use
. For example, a custom hook that manages local storage could be named useLocalStorage
, while a hook that handles authentication could be named useAuthentication
. In our case, we’ll be creating a hook called useFetch
.
Custom Hooks should be composed of one or more built-in React Hooks or other custom Hooks. This means that every custom Hook is essentially a new combination of Hooks. If a custom Hook doesn’t use any Hooks internally, it shouldn’t be considered a custom Hook and should not use the use
prefix.
Let’s Start Creating our own custom hook called useFetch
.
Before start coding we will do brainstorming about what we need this custom hook to do and how much code abstraction we want.
- So generally when we fetch data from api we set that data in a state variable or in store and show that data in UI using JSX.
- We also store error in case if api fail or throw any error we would show proper error message in UI.
- Then we also use a loading state to convey user that there is some processing going on by showing a loader or skeleton screen.
- At last we also want to cancel the previous api call if we make same api call again. This is useful in case where user is searching. We should cancel the previous calls to make sure user see correct data on UI.
Now let’s start the coding 🚀
import { useState, useEffect } from "react"
export const useFetch = ({ url }) => {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url)
const json = await response.json()
setData(json)
setLoading(false)
} catch (error) {
setError(error)
setLoading(false)
}
}
fetchData()
}, [])
return { data, loading, error }
}
Above we have an implementation of the hook which we can use in a component.
function MyComponent() {
const { data, loading, error } = useFetch({
url: "https://jsonplaceholder.typicode.com/todos/1",
})
if (loading) {
return <div>Loading...</div>
}
if (error) {
return <div>Error: {error.message}</div>
}
return <div>{data.title}</div>
}
But this is lacking some features like
- It will only run on component mount. There is no other way to refetch the data.
- This is always run on component mount we can’t skip the call.
Let’s add missing features to this hook
import { useState, useEffect, useCallback } from "react"
export const useFetch = ({ url, skip = false }) => {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const fetchData = useCallback(() => {
;(async () => {
try {
const response = await fetch(url)
const json = await response.json()
setData(json)
setLoading(false)
} catch (error) {
setError(error)
setLoading(false)
}
})()
}, [url])
useEffect(() => {
if (skip) return
fetchData()
}, [fetchData, skip])
return { data, loading, error, refetch: fetchData }
}
Now this hook will the the api call
- When we update the url (if this is a state and cause re-render)
- We are now exporting a new function
refetch
and we can call this function to fetch data again on let’s say a button click. - At last we have added a new config property
skip
. If we set this to true the API call will be skipped and we can use refetch to fetch the data.
Finally we will add the abortController
to cancel the previous api call the we try to make next api call before previous call finish executing.
import { useState, useEffect, useCallback, useRef } from "react"
export const useFetch = ({ url, skip = false }) => {
const abortController = useRef(null)
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const fetchData = useCallback(() => {
;(async () => {
try {
setLoading(true)
abortController.current?.abort()
abortController.current = new AbortController()
const response = await fetch(url, {
signal: abortController.current.signal,
})
const json = await response.json()
setData(json)
setError(null)
setLoading(false)
} catch (error) {
setData(null)
setError(error)
setLoading(false)
}
})()
}, [url])
useEffect(() => {
if (skip) return
fetchData()
}, [fetchData, skip])
return { data, loading, error, refetch: fetchData }
}
By creating custom hooks like this, we are encapsulating complex logic and can reuse this across multiple components, making our code more modular and easier to maintain.
Now to to make a fetch API call in a component we just need a single expression like this.
const { data, loading, error, refetch } = useFetch({
url: "https://jsonplaceholder.typicode.com/todos/1",
skip: true,
})
Overall, React custom hooks are a powerful way for creating reusable and modular code in React applications. They can help reduce duplication, improve code organization, and make it easier to maintain and update complex logic over time.
Checkout following libraries for more custom ready to use hooks