Type-checking with Custom Hooks in React JS

Introduction

React hooks allow us to manage state, side effects, context, and other logic in functional components. Custom hooks enable us to reuse logic across multiple components. TypeScript helps ensure that the data managed by these custom hooks is properly typed, which improves code quality and prevents runtime errors. This tutorial will show you how to type-check custom hooks in React using TypeScript.

Step 1: Setting Up TypeScript in Your React Project

If you haven't already set up TypeScript in your React project, you can create a new TypeScript-based React app with the following command:


  npx create-react-app my-app --template typescript
      

For an existing React project, you can add TypeScript by running the following:


  npm install --save typescript @types/react @types/react-dom
      

Once TypeScript is installed, rename your JavaScript files from .js to .tsx if you're using JSX.

Step 2: Creating a Custom Hook

Let's start by creating a simple custom hook. In this example, we'll create a custom hook that manages a counter state.

Example: Creating a Counter Hook

Create a new file called useCounter.ts and add the following code:


  import { useState } from 'react';
  
  // Define the type for the state
  function useCounter(initialValue: number = 0) {
    const [count, setCount] = useState(initialValue);
  
    const increment = () => setCount(count + 1);
    const decrement = () => setCount(count - 1);
  
    return { count, increment, decrement };
  }
  
  export default useCounter;
      

In this custom hook, we are using the useState hook to manage the count state. The type of count is explicitly set to number using TypeScript's type annotations. The custom hook returns the count value and two functions to modify it.

Step 3: Using the Custom Hook in a Component

Now, let's use the useCounter custom hook inside a functional component. We will also ensure that the custom hook’s return values are properly typed.

Example: Using the Custom Hook in a Component

Create a new file called CounterComponent.tsx and add the following code:


  import React from 'react';
  import useCounter from './useCounter';
  
  const CounterComponent: React.FC = () => {
    const { count, increment, decrement } = useCounter();
  
    return (
      

Count: {count}

); }; export default CounterComponent;

In this example, we use the useCounter hook inside the CounterComponent component. The useCounter hook returns an object with the count, increment, and decrement values, and we destructure those values for use in the component.

Step 4: Type-checking the Custom Hook's Return Values

TypeScript allows you to explicitly type the return value of your custom hooks. You can define the return type to ensure that the hook returns the expected values.

Example: Typing the Return Value of the Custom Hook

Let's update the useCounter hook to type its return value. We'll define a custom type for the return value of the hook:


  import { useState } from 'react';
  
  // Define a custom type for the return value
  interface UseCounterReturn {
    count: number;
    increment: () => void;
    decrement: () => void;
  }
  
  // Define the hook with typed return value
  function useCounter(initialValue: number = 0): UseCounterReturn {
    const [count, setCount] = useState(initialValue);
  
    const increment = () => setCount(count + 1);
    const decrement = () => setCount(count - 1);
  
    return { count, increment, decrement };
  }
  
  export default useCounter;
      

In this version of the useCounter hook, we define an interface UseCounterReturn that specifies the shape of the return value. The hook now explicitly returns an object that matches this interface.

Step 5: Handling More Complex State with Custom Hooks

Custom hooks often handle more complex state, such as objects or arrays. You can type complex states similarly by defining appropriate types or interfaces for them.

Example: Custom Hook with Complex State

Let’s create a custom hook to manage a form state with multiple fields:


  import { useState } from 'react';
  
  // Define the interface for the form state
  interface FormState {
    name: string;
    email: string;
  }
  
  function useForm(initialState: FormState) {
    const [formState, setFormState] = useState(initialState);
  
    const handleChange = (e: React.ChangeEvent) => {
      const { name, value } = e.target;
      setFormState((prevState) => ({
        ...prevState,
        [name]: value,
      }));
    };
  
    return { formState, handleChange };
  }
  
  export default useForm;
      

In this example, we define an interface FormState that describes the shape of the form state, including name and email properties. The custom hook useForm handles changes to the form fields and returns the current state and the change handler function.

Step 6: Using the Complex Hook in a Component

Now, let’s use the useForm hook inside a React component. We will also make sure that the form state is type-checked properly.

Example: Using the Complex Hook in a Component

Create a new file called FormComponent.tsx and add the following code:


  import React from 'react';
  import useForm from './useForm';
  
  const FormComponent: React.FC = () => {
    const { formState, handleChange } = useForm({ name: '', email: '' });
  
    return (
      

Name: {formState.name}

Email: {formState.email}

); }; export default FormComponent;

In this example, we use the useForm custom hook to manage the state of the form. The form fields are type-checked based on the FormState interface, and the input fields are controlled by the form state.

Step 7: Best Practices for Typing Custom Hooks

Here are some best practices to follow when typing custom hooks:

  • Always define the return type of your custom hook using an interface or type alias to ensure consistency.
  • For hooks that manage complex state, create clear and concise types for the state structure to prevent bugs and confusion.
  • Use TypeScript's utility types (e.g., Partial, Pick, Omit) to make your hooks more reusable and flexible.

Conclusion

Type-checking custom hooks with TypeScript is an effective way to ensure type safety and improve code quality in React applications. By defining clear types for the state and return values of your custom hooks, you can prevent common errors and make your codebase more maintainable.





Advertisement