3 Essential React Hooks You Should Have in Your Project
Tags: javascript, react, typescript
Published: 2024-05-22 21:42:46

React hooks are powerful tools for managing state and side effects in your applications. While hooks like `useState`, `useEffect`, and `useReducer` are built into React, it's often beneficial to create custom hooks tailored to your specific needs. Here are three essential custom hooks that you should consider adding to your project:

### 1. `useFetch`

Handling requests to a backend server is a common task in almost every project. Instead of using the native JavaScript `fetch` function repeatedly, you can create a custom hook to manage this logic:

```typescript
import { useEffect, useState } from "react";

/**
 * Custom hook to fetch data from a given URL.
 *
 * @template T - The type of the data being fetched.
 * @param {string} url - The URL to fetch data from.
 * @param {RequestInit | undefined} options - The options to pass to the fetch request.
 * @returns {[T | null, boolean, unknown | null]} - An array containing the fetched data, loading state, and error state.
 */
export function useFetch<T>(url: string, options?: RequestInit): [T | null, boolean, unknown | null] {
  // State to store the fetched data
  const [data, setData] = useState<T | null>(null);

  // State to store the loading status
  const [isLoading, setIsLoading] = useState<boolean>(false);

  // State to store any error that occurs during the fetch
  const [error, setError] = useState<unknown | null>(null);

  // useEffect hook to trigger the fetch operation whenever the URL or options change
  useEffect(() => {
	fetchData();
  }, [url, options]);

  /**
   * Function to fetch data from the provided URL.
   *
   * This function handles the fetch operation, updates the loading state,
   * sets the fetched data, and handles any errors that occur during the fetch.
   */
  async function fetchData(): Promise<void> {
	// Set loading state to true before starting the fetch
	setIsLoading(true);

	try {
  	// Perform the fetch operation
  	const response = await fetch(url, options);

  	// Parse the response data as JSON
  	const responseData = await response.json();

  	// Update the state with the fetched data
  	setData(responseData);

  	// Clear any previous error
  	setError(null);
	} catch (error) {
  	// Update the state with the error if the fetch fails
  	setError(error);
	} finally {
  	// Set loading state to false after the fetch is complete
  	setIsLoading(false);
	}
  }

  // Return the fetched data, loading state, and error state
  return [data, isLoading, error];
}
```

### 2. `useLocalStorage`

Storing data on the client side, such as in `localStorage`, is a common requirement for caching and saving useful information. This custom hook helps you get and set data in `localStorage`:

```typescript
import { useEffect, useState } from "react";

/**
 * A custom hook that synchronizes a state variable with localStorage.
 *
 * @template T The type of the stored value.
 * @param {string} key The key under which the value is stored in localStorage.
 * @returns {[T | null, (value: T) => void]} A stateful value and a function to update it.
 *
 * @example
 * const [name, setName] = useLocalStorage<string>('name');
 * setName('Alice');
 * console.log(name); // 'Alice'
 */
export function useLocalStorage<T>(
  key: string,
): [T | null, (value: T) => void] {
  // Initialize state with null
  const [data, setData] = useState<T | null>(() => null);

  // useEffect to retrieve and set the data from localStorage on mount and when the key changes
  useEffect(() => {
	setData(() => {
  	// Get the data from localStorage
  	const localStorageData = localStorage.getItem(key);

  	// If data exists, parse and return it; otherwise, return null
  	if (localStorageData) {
    	return JSON.parse(localStorageData);
  	} else {
    	return null;
  	}
	});
  }, [key]); // Dependency array with key ensures this runs when key changes

  /**
   * Updates the localStorage value and the state.
   *
   * @param {T} value The value to store.
   */
  function setLocalStorage(value: T): void {
	// Store the value in localStorage
	localStorage.setItem(key, JSON.stringify(value));

	// Update the state to trigger rerender
	setData(() => value);
  }

  // Return the stateful value and the updater function
  return [data, setLocalStorage];
}
```

### 3. `useToggle`

One of the simplest yet most useful hooks is `useToggle`. This hook manages a boolean state, making it easy to toggle between `true` and `false`. This is particularly useful for UI elements like dropdowns:

```typescript
import { useState, Dispatch, SetStateAction } from "react";

/**
 * A custom hook that provides a boolean state with a toggle function.
 *
 * @param {boolean} [initialValue=false] The initial value of the toggle state.
 * @returns {[boolean, () => void, Dispatch<SetStateAction<boolean>>]} An array containing:
 *   - The current boolean state.
 *   - A function to toggle the state.
 *   - The state setter function.
 *
 * @example
 * const [isOpen, toggleIsOpen, setIsOpen] = useToggle(false);
 * toggleIsOpen(); // Toggles the state
 * console.log(isOpen); // true or false
 */
export function useToggle(
  initialValue?: boolean
): [boolean, () => void, Dispatch<SetStateAction<boolean>>] {
  // Initialize the state with the initial value or default to false
  const [isToggle, setIsToggle] = useState<boolean>(() => initialValue ?? false);

  /**
   * Function to toggle the boolean state.
   */
  function toggle(): void {
	// Toggle the state value
	setIsToggle(value => !value);
  }

  // Return the current state, the toggle function, and the state setter
  return [isToggle, toggle, setIsToggle];
}
```

## Conclusion

Understanding and utilizing React hooks is key to creating powerful and efficient applications. While it's common to write logic directly inside React components, repetitive patterns suggest it's time to create custom hooks. With `useFetch`, `useLocalStorage`, and `useToggle`, you can enhance your toolkit and streamline your development process.

What custom hook do you use the most? Let me know in the comments below! :D