A Beginner's Guide to React Custom Hooks
Tags: react, beginners, tutorial, javascript
Published: 2023-03-25 11:53:38

React hooks have been a game changer for developers since they were introduced in React 16.8. Hooks give developers access to **[state](https://react.dev/learn/state-a-components-memory#adding-a-state-variable)** and other React features without using class components. This tutorial will discuss how to create custom React hooks.

## Prerequisites

To understand this tutorial, you should have a basic understanding of React, JavaScript, and ES6 syntax. It's also recommended that you have some experience using **[hooks](https://react.dev/reference/react)**.

## A Quick Overview of React Hooks

Before hooks came along, state and other React features could only be used in class components. With hooks, you can create reusable stateful logic that can be shared across multiple components. Some of the commonly used React hooks are **[useState](https://react.dev/reference/react/useState)**, **[useEffect](https://react.dev/reference/react/useEffect)**, **[useContext](https://react.dev/reference/react/useContext)**, and **[useRef](https://react.dev/reference/react/useRef)**.

## Rules Guiding Usage of React Hooks

There are a couple of rules that should be followed when creating and using hooks:

- Names of hooks must start with "use" to indicate that it is a hook.
```jsx
 const useMyHook = () => {/*....*/}
    
 const useAnotherHook = () => {/*....*/}
```

- Hooks should only be used at the top level of a functional component or another hook. They should not be used inside loops, conditions, or nested functions as they can lead to unexpected behavior.
    
```jsx
  const MyComponent = () => {
      const items = [];
    
      if(items.length > 2) {
        useMyHook(); // do not do this
      }
    
      items.forEach(item => useMyHook()); // do not do this
    
      return <p>My component</p>
  }
```
    
- Hooks must only be called from within a functional component or another hook. They cannot be called from regular JavaScript functions.
    
```jsx
 const myFunction = () => {
   const hookResult = useMyHook(); // wrong!
 }
    
 const useMyHook = () => {
   const other = useMyOtherHook(); // correct!
   // .............
   // .............
 }
    
 const MyComponent = () => {
   const myHook = useMyHook();
    
   return <p>My Component</p>
 }   
```
    
## Why Create Custom Hooks

Creating custom hooks is useful for quite a number of reasons:

- Custom hooks allow us to extract and reuse stateful logic in our React components. By encapsulating stateful logic in a custom hook, we can make our code more modular and easier to maintain.
- Custom hooks allow us to separate concerns in our code, making it easier to read and test.

## Creating a Custom Hook

Now that we have a reason to use custom hooks, let’s create three hooks and see them in action:

1. `useWindowWidth`
2. `useLocalStorage`
3. `useFetch`

> 💡 Note: You can find the code for each hook in the [**sandbox**](https://codesandbox.io/s/cool-poitras-51sdc0?file=/src/App.jsx).
> 

### `useWindowWidth`

`useWindowWidth` allows us to get the current width of the window. It uses the `resize` event to update the window's width whenever the window is resized:

```jsx
// /src/hooks/useWindowWidth.js
import { useState, useEffect } from "react";

// notice that the hook name starts with `use`
export const useWindowWidth = () => {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };

    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  return { windowWidth };
};

```

With this hook present, we don’t have to write the logic for resizing the window every time we want to get the window’s width in a component.

Also notice that we are returning a value. It’s common but not compulsory to return any JavaScript data type from a hook. We are returning the `windowWidth` in an object so that it will be easily destructured.

We will call our hook at the top level of any component that wants to use it:

```jsx
// /src/components/Window.jsx
import { useWindowWidth } from "./../hooks/useWindowWidth";

const Window = () => {
  const { windowWidth } = useWindowWidth();

  return (
    <section>
      <h1>Window width:</h1>
      <p>{windowWidth}</p>
    </section>
  );
};

export default Window;

```

### `useLocalStorage`

`useLocalStorage` allows us to store data in the browser’s local storage and retrieve it in a different component:

```jsx
// /src/hooks/useWindowWidth.js
import { useState } from "react";

export const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    const localVal = localStorage.getItem(key);

    return localVal === null ? initialValue : JSON.parse(localVal);
  });

  const updateValue = (newValue) => {
    setValue(newValue);
    localStorage.setItem(key, JSON.stringify(newValue));
  };

  return { value, updateValue };
};

```

Just like normal functions, custom hooks can also accept any numbers of arguments. We passed in the `key` and an `initial value` in this case:

- `key` represents the local storage key.
- `initialValue` represents a default value to use if there is no value for the `key` in the local storage.

Notice we are returning a `value` and `function` this time around. The `updateValue` can be called just like every other JavaScript function.

We can then use our hook like this:

```jsx
// /src/components/Stack.jsx
import { useLocalStorage } from "./../hooks/useLocalStorage";

const Stack = () => {
  const { value: stack, updateValue: updateStack } = useLocalStorage(
    "stack",
    "JS"
  );

  return (
    <section>
      <h1> Stack: </h1>
      <p>{stack}</p>
      <button onClick={() => updateStack(stack === "JS" ? "TS" : "JS")}>
        Update stack
      </button>
    </section>
  );
};

```

## `useFetch`

`useFetch` fetches data asynchronously. It returns these states:

- `loading` determines if the request is still processing.
- `data` represents the resource we requested.
- `error` represents an error message.

```jsx
// /src/hooks/useFetch.js
import { useEffect, useState } from "react";

export const useFetch = (url) => {
  const [resource, setResource] = useState({
    loading: true,
    error: null,
    data: null
  });

  useEffect(() => {
    const fetchData = async () => {
      try {
        const req = await fetch(url);
        const data = await req.json();
        setResource((prev) => ({ ...prev, data }));
      } catch (error) {
        setResource((prev) => ({ ...prev, error: error.message }));
      } finally {
        setResource((prev) => ({ ...prev, loading: false }));
      }
    };

    fetchData();
  }, []);

  return resource;
};

```

> 💡 Note: This useFetch isn't production ready.
> 

We can then call our hook in any component like this:

```jsx
// /src/components/Posts.jsx
import { useFetch } from "./../hooks/useFetch";

const postsUrl = "<https://jsonplaceholder.typicode.com/posts>";

const Posts = () => {
  const { data, loading, error } = useFetch(postsUrl);

  if (loading) return <div>Loading</div>;

  if (error) return <div>Error</div>;

  const posts = [...data].slice(1, 10);

  return (
    <section>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.body}</li>
        ))}
      </ul>
    </section>
  );
};

export default Posts;

```

## Custom Hook Share Stateful Logic, not State itself

Custom hooks are meant to share stateful logic, and not state itself. Two different components using the same custom hook are not sharing the same state that the custom hook provides. This means that you should not use a custom hook to share the actual state between components. When you need to share the state itself between multiple components, [lift it up to a parent and pass it down](https://react.dev/learn/sharing-state-between-components) instead.

Let's look at our `useLocalStorage` for an example. Assume that we have another component, `Profile`, that calls the hook like this:

```jsx
// /src/components/Profile.jsx
import { useLocalStorage } from "../hooks/useLocalStorage";

const Profile = () => {
  const { value: gender, updateValue: updateGender } = useLocalStorage(
    "gender",
    "Male"
  );

  return (
    <section>
      <h1> Gender: </h1>
      <p>{gender}</p>
      <button
        onClick={() => updateGender(gender === "Male" ? "Female" : "Male")}
      >
        Update gender
      </button>
    </section>
  );
};

export default Profile;

```

The `Stack` and `Profile` components are not sharing the same state even though they are calling the `useLocalStorage` hook. Updating our stack with the function returned by `useLocalStorage` won't update the gender, likewise updating the `gender` won't update the `stack`.

{% embed https://codesandbox.io/embed/cool-poitras-51sdc0?fontsize=14&hidenavigation=1&theme=dark %}

## Conclusion

Custom React hooks are a powerful tool for creating reusable stateful logic in your React applications. They allow you to separate concerns and make your code more modular and easier to maintain. By following the rules for using hooks and starting with simple examples, you can create custom hooks that are easy to use.

You can read more about custom hooks from the [react documentation](https://react.dev/learn/reusing-logic-with-custom-hooks).

Happy coding.