DEV Community

Cover image for Guide to using React Custom Hooks
Ismail Adekunle Adegbite
Ismail Adekunle Adegbite

Posted on

Guide to using React Custom Hooks

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:

  1. 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.
  2. Reusability: Custom Hooks can be used in multiple components, making it easier to reuse code and reduce duplication.
  3. 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.
  4. 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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 and setLoading : loading indicates if the request has been commenced and pending. Its initial value is set to false. setLoading is used to update its value as appropriate.
  • error and setError : error this is to indicate if the request is rejected or failed and setError to update its value as appropriate
  • data and setData : data is to be updated to the resource returned from the request. The setData 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>
                )
            })
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

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!!!

Top comments (0)