codewithjohn.dev
Published on

Building Custom React Hooks with TypeScript

In this article, we will explore the process of creating custom React hooks using TypeScript, along with a practical code example.

React hooks were introduced in React version 16.8 they revolutionized the way we write functional components in React. Creating custom Hooks in React allows developers reuse logic across multiple components. If you want to read More info about hooks can be found Here.

Rule of Hooks

There are a few rules to follow to create a custom hooks

  • The name should always start with the word use to indicate that they are hooks.
  • Only Call Hooks at the Top Level
    • Hooks should always be called at the top level of a functional component or another custom hook.
    • Do not call hooks inside loops, conditions, or nested functions.
  • Hooks can only be used in functional components, not in class components.

For this article we are going to create a custom hook called useFetchData that is used to fetch data from API. This hook will return the loading state, the fetched data, and an error message if any.

First, let’s create the file useFetchData.ts and define the FetchDataReturnType type, which describes the shape of the values returned by our hook.

type FetchDataReturnType<T> = {
  data: T | null
  loading: boolean
  error: string | null
}

The FetchDataReturnType type itself is an object type with three properties:

  • data: T | null: This property represents the actual data that is returned by the hook and has a type of T, which is generic type parameter. The T | null union type allows the data property to be either of type T or null.

  • loading: boolean: This property represents the loading state and has a type of boolean. It indicates whether the data is currently being fetched or done fetching.

  • error: string | null: This property represents the error message and has a type of string or null.

Now let's implement the actual logic of our hook

import { useState, useEffect } from 'react'

type FetchDataReturnType<T> = {
  data: T | null
  loading: boolean
  error: string | null
}

function useFetchData<T>(url: string): FetchDataReturnType<T> {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState<boolean>(true)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    const fetchData = async (): Promise<void> => {
      try {
        const response = await fetch(url)
        const jsonData = await response.json()
        setData(jsonData)
      } catch (e) {
        setError('Error fetching data')
      } finally {
        setLoading(false)
      }
    }

    fetchData()
  }, [url])

  return { data, loading, error }
}

export default useFetchData

Inside the above example, we are using the useState hook to manage the state of data, loading, and error. We also use the useEffect hook to fetch the data from the provided URL when the component mounts and and when the url state changes.

We also create fetchData function that makes the API call and sets the data state if the API call is successful and sets the error state if an error occurs during the fetch,

We are also explicitly annotating the return type of the hook as FetchDataReturnType.

Using this custom hook in a component would look like this:

import React from 'react'
import useFetchData from './useFetchData'

type User = {
  id: number
  name: string
  email: string
}

const UserList: React.FC = () => {
  const { data, loading, error } = useFetchData<User[]>('https://api.example.com/users')

  if (loading) {
    return <div>Loading...</div>
  }

  if (error) {
    return <div>{error}</div>
  }

  return <ul>{data?.map((user) => <li key={user.id}>{user.name}</li>)}</ul>
}

export default UserList

In the above example, we are using our custom hook useFetchData hook to fetch a list of users from an API. Since our hook is generic, we created a User type to indicate that the fetched data will be an array of User objects.

Inside the component, we are accessing the data, loading, and error properties returned from the hook. Based on the state, we can render loading and error messages,

Conclusion

React hooks are a very powerful way for developers to reuse logic across multiple components and make extensible and maintainable code that is easy to read and understand. Using TypeScript with React Hooks provides static typing and helps catch errors early in the development process. After reading this article, I hope you feel confident about building your own custom hooks with TypeScript in your React projects. Happy coding!

If you're interested in learning more about React and TypeScript, check out our React TypeScript guide and the official TypeScript documentation.

Like what you are reading?