Guide to using React Custom Hooks
Ismail Adekunle Adegbite
Posted on May 8, 2023
Many a times in your application, you may need to perform an operation in different components. Of course you can write a function for this purpose and then import such function in the respective components to achieve its intended purpose. What if the operation requires to call one or more React hooks, then the function cannot be used to perform such operations as React hooks can only be called in React functions. To solve this, we make use of React custom hooks. In this blog post, we will dive into what React custom hooks are and how to use them to encapsulate any resuable logic that you may need to share across multiple components.
What are React Custom Hooks
React Custom Hooks are JavaScript functions that use React hooks to provide a set of reusable functionality that can be shared among different React components. Custom Hooks are named with the use
prefix to signal that they are intended to be used with hooks. As a convention, the file where a custom hook is written is usually named with the use
prefix for example useMediaQuery.js
. But it is required that the name of a function intended to be used as a React custom hook be started with use
.
It is important to note that custom hooks are React hooks we write ourselves and hence their usage must conform to using in-built React hooks. Rules for using React (custom and in-built) hooks are:
- Only call hooks at the top level components and before any early returns
- Only call hooks from React functions which include React functional components and hooks (custom and in-built)
Why use React Custom Hooks?
React Custom Hooks provide several benefits which include the following:
- Encapsulation: Custom Hooks allow you to encapsulate complex logic and state management in a single location which can then be shared among different components in your project. This makes your code more modular and easier to maintain.
- Reusability: Custom Hooks can be used in multiple components, making it easier to reuse code and reduce duplication.
- Testability: By encapsulating complex logic in Custom Hooks, it becomes easier to write tests for your code, since you can test the logic in isolation from the components that use it.
- Code organization and seperation of concerns: Custom Hooks allow you to separate concerns (views from logic) and organize your code into smaller, more manageable pieces.
Examples of React Custom Hooks
Let's dive into two scenarios where React custom hooks can be used.
Our first example will be useMediaQuery
. Let's say you have a component in which you want to hide/display some elements or perform any other operation at certain sreen sizes, then you will have to keep track of changes in width of user's device. Here you will need a state variable to get the current screen size as the screen changes and resize
event listener to be attached to the window.
The component may be written as shown:
import { useState, useEffect } from "react"
const MyComponent = () => {
const [screenWidth, setScreenWidth] = useState(0)
useEffect(() => {
const getScreenWidth = () => {
setScreenWidth(window.innerWidth)
}
getScreenWidth()
window.addEventListener('resize', getScreenWidth)
return () => {
window.removeEventListener('resize', getScreenWidth)
}
}, [])
return (
<div className='container'>
Current screen width: {screenWidth}
{screenWidth > 768 && <p>Show only on screen greater than 768px</p> }
<p>Show on all screen size</p>
</div>
)
}
The above implementation will work well. What if you have more of such components in your application where you may need to hide or display some content or perform any other opearation depending on the width of the screen at any instance of time.
What do you do? Would you create a function for getting the screen width every time the window is resized? Remember this involves a state value and you can only declare/call useState
and any other React hooks such as useEffect
, useRef
etc at top level in a React functional component or hook (in-built and custom).
We can isolate the logic of getting the current size (width) of screen at any time as the window is resized with a custom hook as shown below:
import { useState, useEffect } from "react"
const useMediaQuery = () => {
const [screenWidth, setScreenWidth] = useState()
useEffect(() => {
const getScreenWidth = () => {
setScreenWidth(window.innerWidth)
}
getScreenWidth()
window.addEventListener('resize', getScreenWidth)
return () => {
window.removeEventListener('resize', getScreenWidth)
}
}, [])
return screenWidth
}
export default useMediaQuery
You can then import this custom hook in as many components as required in your project to get the current screen size at any time.
Then our MyComponent
can be refactored as shown below
const MyComponent = () => {
// make sure you import useMediaQuery as necessary
const screenWidth = useMediaQuery()
return (
<div>
{screenWidth}
{screenWidth > 768 && <p>Show only on screen greater than 768px</p> }
<p>Show on all screen size</p>
</div>
)
}
Make sure you import the useMediaQuery
as necessary into any components it is required. For example if useMediaQuery
is written in a file named useMediaQuery.js
inside a folder named /src/hooks
and exported as default from the file as shown below:
// /src/hooks/useMediaQuery.js
import { useState, useEffect } from "react"
const useMediaQuery = () => {
const [screenWidth, setScreenWidth] = useState()
useEffect(() => {
const getScreenWidth = () => {
setScreenWidth(window.innerWidth)
}
getScreenWidth()
window.addEventListener('resize', getScreenWidth)
return () => {
window.removeEventListener('resize', getScreenWidth)
}
}, [])
return screenWidth
}
export default useMediaQuery
Then inside /src/components/MyComponent.js
it will be imported as used as shown below
// /src/components/MyComponent.js
import useMediaQuery from '../useMediaQuery'
const MyComponent = () => {
const screenWidth = useMediaQuery()
return (
<div className='container'>
Current screen width: {screenWidth}
{screenWidth > 768 && <p>Show only on screen greater than 768px</p> }
<p>Show on all screen size</p>
</div>
)
}
Our second custom hook will be the useFetch
hook that can be used across multiple components for data fetching. The hook may be written as follows:
// /src/hooks/useFetch.js
import { useState, useEffect } from 'react'
const useFetch = (url) => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [data, setData] = useState(null)
useEffect(() => {
// set loading to true to indicate the request is pending
setLoading(true)
fetch(url)
.then(response => {
// check if the request is successful
if (!response?.ok) {
throw new Error('an error occurred')
}
return response.json()
})
.then(responseData => {
setData(responseData)
setLoading(false)
setError(null)
})
.catch(error => {
setLoading(false)
setError(error)
})
}, [url])
return {
loading,
data,
error
}
}
export default useFetch
Here, three state variables and the corresponding functions to update their values are initiated. The useFetch
hook receives a parameter, which is the endpoint to fetch the required data. Depending on your use case, any custom hook can be defined to receive any number of paramters to achieve an intended purpose.
-
loading
andsetLoading
:loading
indicates if the request has been commenced and pending. Its initial value is set tofalse
.setLoading
is used to update its value as appropriate. -
error
andsetError
:error
this is to indicate if the request is rejected or failed andsetError
to update its value as appropriate -
data
andsetData
:data
is to be updated to the resource returned from the request. ThesetData
is to achieve this purpose.
After initializing the state variables required, the React useEffect
is use to implement the logic required for fectching the data and updating the variables values.
The value of loading
is first updated to true
to indicate the request has been commenced and is pending. Then we made the request to the endpoint, the value of which will be the value of the required url
parameter.
The response status is checked if the request is fulfilled, that is succesful. Upon success we update the state variables as shown in the code and also upon failed request.
Note any other external library such as axios can be used instead of the in-built fetch
API
Now let's use our custom useFetch
hook to fetch blog post from a dummy endpoint.
Let's make a seperate component where, useFetch
hook will be called as shown below:
// src/components/Posts.js
import useFetch from '../hooks/useFetch'
const Posts = () => {
const {
loading,
error,
data
} = useFetch('https://jsonplaceholder.typicode.com/posts/')
if (loading) {
return <p>loading...</p>
}
if (error) {
return <p>Unable to fetch data</p>
}
// check if returned data is truthy.
// You may want to check for other thing depending on the type and structure of the expected data
if (data) {
return (
data.map((post) => {
return (
<article key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</article>
)
})
)
}
}
Here, the useFetch
custom is imported and called in the Posts
component. The returned object is destrutured to obtain the loading
, error
and data
state values after destructuring.
We check the values of these in order to render appropriate components/elements.
Conclusion
We have looked into how React Custom Hooks can make our code conforms to DRY (Don't Repeat Yourself) principle by enabling our code to be modular and reusable. You thoughts and comments are highly welcomed. Thank you!!!
Posted on May 8, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.