DEV Community

Cover image for A Guide to React Custom Hooks
Rasaf Ibrahim
Rasaf Ibrahim

Posted on

A Guide to React Custom Hooks

Custom Hooks in React are a powerful feature that allows us to extract component logic into reusable functions. These Hooks are JavaScript functions that can use other Hooks provided by React. They enable us to organize logic into separate, reusable modules.

Β 

πŸ“Œ Table of Contents

Β 

Prerequisites

Β 

Before diving into custom Hooks, it's important to have a basic understanding of React and its core concepts, such as components, state, and built-in Hooks like useState and useEffect.

Β 

Our First Custom Hook

Β 

Let's start by creating a simple custom Hook. We’ll build a useCounter hook that encapsulates the logic for incrementing a counter.

import { useState } from 'react'

// Custom hook for counter functionality
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue) // State to keep track of count

  // Function to increment count
  const increment = () => setCount(prevState => prevState + 1)

  // Returns count and increment function
  return [count, increment]
}

Enter fullscreen mode Exit fullscreen mode

To use our useCounter Hook, we can include it in a functional component:

import React from 'react';
import useCounter from './useCounter'

// Component using the useCounter Hook
function CounterComponent() {
  const [count, increment] = useCounter() // Utilizing useCounter

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Β 

Tips for Building Custom Hooks

Β 

  • Start by identifying repetitive logic across your components.
  • Extract this logic into a function named with use prefix. Custom hooks should start with use, like useFormInput or useFetch.
  • Use React's built-in Hooks within your custom Hook as needed.
  • Return anything that will be useful for the component using this Hook.
  • Each custom hook should be responsible for a single piece of functionality.

Β 

Sharing Logic with Custom Hooks

Β 

Custom Hooks are excellent for sharing logic across multiple components. In this section, we will create two common custom hooks that React developers often create to share logic across multiple components.

Β 

Building a Data Fetching Hook

Β 

If we need to fetch data from an API in several components, we can create a useFetch Hook. Here's a simple implementation of useFetch:

import { useState, useEffect } from 'react'

// Custom hook for fetching data
function useFetch(url) {

  const [data, setData] = useState(null) // State for data
  const [loading, setLoading] = useState(true) // State for loading
  const [error, setError] = useState(null) // State for error handling

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true)
      setError(null)

      try {
        const response = await fetch(url)

        if (!response.ok) {
          throw new Error(`An error occurred: ${response.statusText}`)
        }

        const jsonData = await response.json()
        setData(jsonData)
      } catch (error) {
        setError(error.message)
      } finally {
        setLoading(false)
      }
    }

    fetchData()
  }, [url]) // Dependency array with url

  return { data, loading, error }
}
Enter fullscreen mode Exit fullscreen mode

We can now use this Hook in our components to fetch data easily:

import React from 'react'
import useFetch from './useFetch'

// Component using the useFetch Hook
function DataDisplayComponent({ url }) {
  const { data, loading, error } = useFetch(url) // Utilizing useFetch

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error: {error}</p>

  return <div>{JSON.stringify(data, null, 2)}</div>
}
Enter fullscreen mode Exit fullscreen mode

Β 

Building a Form Handling Hook

Β 

Handling forms in React can be verbose. A custom hook, useForm, can simplify this. Here’s how we might structure it:

import { useState } from 'react'

function useFormInput(initialValue) {
  const [value, setValue] = useState(initialValue)

  const handleChange = (e) => {
    setValue(e.target.value)
  }

  // Returns an object with value and onChange properties
  return {
    value,
    onChange: handleChange
  }
}
Enter fullscreen mode Exit fullscreen mode

We can now use this Hook in our form related components:

import React from 'react'
import useFormInput from './useFormInput'

function FormComponent() {

  const name = useFormInput('')
  const age = useFormInput('')

  const handleSubmit = (e) => {
    e.preventDefault()
    console.log(`Name: ${name.value}, Age: ${age.value}`)
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Name:
          {/* The spread operator here is equivalent to value={name.value} onChange={name.onChange} */}
          <input type="text" {...name} />
        </label>
      </div>
      <div>
        <label>
          Age:
          <input type="number" {...age} />
        </label>
      </div>
      <button type="submit">Submit</button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ The implementation of useFormInput provided here is a basic version, designed for straightforward form input management. If additional functionalities are required, such as form validation or handling more complex form patterns, the Hook should be further customized and extended.

Β 

Wrapping Up

Β 

Custom Hooks in React offer a powerful and elegant way to reuse and share logic across components. By understanding and leveraging this feature, we can significantly simplify our components and improve our code's maintainability and readability.

Β 

rich-text-editor-for-react npm package

Demo | Documentation

Top comments (8)

Collapse
 
brense profile image
Rense Bakker

If you build custom hooks, its very important to memoize everything that you return from that hook. For example the increment function should be wrapped in a useCallback. Otherwise any side effect or child component that uses the values returned by the hook, will always trigger on every rerender, even if the values didn't really change.

Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim

Thanks for your insights on memoizing in custom hooks. You're spot on about the importance of using useCallback for functions like increment to prevent unnecessary re-renders in certain scenarios, such as when passing callbacks to child components or using them as dependencies in useEffect.

However, it's also important to consider that memoization isn't always necessary and can sometimes add complexity. While it's crucial in cases like callback functions, over-memoizing can lead to performance overhead. It's about finding the right balance based on the specific use case. Your point is well-taken and reminds us to thoughtfully consider when and how to apply memoization effectively.

Collapse
 
seandinan profile image
Sean Dinan • Edited

Very nice using the spread operator for passing everything into the <input/>! Keeps things clear & concise :)

Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim

I agree, using the spread operator does help keep things clear and concise. I appreciate your feedback! πŸŽ‰

Collapse
 
patric12 profile image
Patric

Thanks for this clear and insightful article on React Custom Hooks!

Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim

Thanks for your feedback. πŸŽ‰

Collapse
 
ademagic profile image
Miko

Great article!

(Custom) Hooks are JavaScript functions that can use other Hooks provided by React

Probably the most effective sentence I've ever read at describing hooks/custom hooks

Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim

Thank you. I appreciate your feedback! πŸŽ‰