Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 49

Sharing Logic with Custom Hooks

Recommended resources

Hello again!

In our last lesson, you did an excellent job enhancing our useCounter hook. You moved beyond just creating it and made it truly interactive by returning functions to increment, decrement, and reset the count. You now have a self-contained, reusable piece of stateful logic.

Today, we're going to put the "reusable" part to the test. Our goal is to see what happens when we use this same hook in multiple components at the same time. This will reveal one of the most powerful and fundamental concepts behind custom hooks in React.

By the end of this lesson, you will be able to reuse a custom hook across multiple components to share stateful logic.

The Core Principle: Sharing Logic, Not State

Before we dive into code, let's address a key question: If two different components both call useCounter(), will they share the same count value?

The answer is no. This is a crucial concept to grasp:

Custom Hooks let you share stateful logic, but not the state itself.

Think of a custom hook as a recipe. If you use the same recipe to bake two separate cakes, you are reusing the same set of instructions (the logic), but you end up with two entirely separate cakes (the state). Each cake has its own ingredients and can be baked independently.

Each time a component calls a hook like useState, useEffect, or a custom hook like useCounter, it gets its own isolated "copy" of that hook's state and effects.

The official React documentation has a fantastic section explaining this.

Reusing Logic with Custom Hooks - React

Let's read the definitive explanation of this principle from the React team. This section uses a useFormInput hook to illustrate how two inputs can use the same hook without sharing their values.

Please read the section titled Custom Hooks let you share stateful logic, not state itself. It clearly compares what happens before and after extracting the hook to show that the behavior is identical.

This diagram provides a simple visual for this concept. Each component calls useCounter() and gets its own independent count and increase function.

Reusing a Custom Hook Across Multiple Components
Each component calls the `useCounter` hook independently. The logic is shared, but the state (`count`) within each component is separate and does not affect the others.

A Real-World Example: Reusing useFetch

This pattern of reusing logic is extremely common. A classic example is a useFetch hook, designed to fetch data from an API. You might use it on your homepage to get a list of all items, and then use the exact same hook on a details page to fetch just one specific item.

The following video demonstrates this perfectly. It shows how a useFetch hook is first used to get a list of all blog posts and is then reused in a different component to get the details for a single blog post.

Full React Tutorial #26 - Reusing Custom Hooks

This video from The Net Ninja provides a practical demonstration of reusing a custom hook for data fetching. Pay attention to how the same useFetch hook is imported and used in two different components for slightly different tasks.

Please watch from 00:19 to 03:47, and then from 04:42 to 05:10. You'll see how the useFetch hook is implemented in the BlogDetails component and how it simplifies the logic for handling loading, error, and data states.

As you saw in the video, the useFetch hook encapsulates all the logic for:

  • Managing a loading state (isPending).
  • Managing an error state (error).
  • Managing the fetched data (data).

By reusing this hook, both the Home and BlogDetails components get all this functionality without duplicating any code. They simply call the hook with a different URL, and each gets its own independent set of data, isPending, and error states.


Your Turn: Reuse Your useCounter Hook

Now it's time to see this in action with your own code. You'll use the useCounter hook you perfected in the last lesson and apply it to two separate counter components. This exercise will let you prove to yourself that their states are independent.

Let's imagine you're building an e-commerce page where a user can adjust the quantity of two different items in their cart. Each item needs its own counter.

Starter Code:

You'll need three files for this: useCounter.js, App.js, and a new file, ItemCounter.js.

1. useCounter.js (from the previous lesson)
Make sure your useCounter.js file contains the logic you wrote previously, which should look something like this:

// useCounter.js
import { useState } from 'react';

export function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(initialValue);

  // You can return an object or an array. An object is clear here.
  return { count, increment, decrement, reset };
}

2. ItemCounter.js (New Component)
Create this new component. It will represent a single counter UI.

// ItemCounter.js
import { useCounter } from './useCounter';

export default function ItemCounter({ itemName }) {
  // Use your custom hook here!
  const { count, increment, decrement, reset } = useCounter();

  return (
    <div style={{ border: '1px solid #ccc', padding: '16px', margin: '8px' }}>
      <h3>{itemName}</h3>
      <p>Quantity: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

3. App.js (Main Component)
Your main App component will now render two instances of ItemCounter.

// App.js
import ItemCounter from './ItemCounter';

export default function App() {
  return (
    <div>
      <h1>Shopping Cart</h1>
      <ItemCounter itemName="Audio Cable" />
      <ItemCounter itemName="USB Hub" />
    </div>
  );
}

Instructions:

  1. Assemble the three files as shown above in your project.
  2. Run the application.
  3. Interact with the buttons for the "Audio Cable". Increment, decrement, and reset its quantity.
  4. Observe the "USB Hub" counter. Does its quantity change?
  5. Now, interact with the "USB Hub" counter. Does it affect the "Audio Cable" counter?

You should see that each ItemCounter component maintains its own, completely separate count state, even though they are both powered by the exact same useCounter hook. You have successfully shared logic without sharing state!


Why This Pattern Is So Powerful

Extracting stateful logic into reusable hooks is one of the most significant features of modern React. Let's quickly recap the benefits.

Custom Hooks in React - Every React Developer Should Know This

This short clip from ByteGrad summarizes the key advantages of using custom hooks.

Watch from 18:56 to 20:13. The video clearly explains how custom hooks lead to better reusability, cleaner components, and improved readability.

The main advantages are:

  • Reusability: You write the logic once and use it anywhere.
  • Cleanliness & Readability: Component code becomes declarative. Instead of seeing complex useEffect and useState calls, you see a single line like const { data, loading } = useFetch(...). The name of the hook itself describes the logic being implemented.
  • Maintainability: If there's a bug in your data-fetching logic, you only have to fix it in one place (the useFetch hook), and the fix will apply everywhere it's used.

Conclusion

Fantastic work! You've now grasped the full power of custom hooks by seeing how they enable logic to be shared across your application while keeping component state neatly encapsulated.

Key Takeaways:

  • Custom hooks are designed to share stateful logic, not the state itself.
  • Each component that calls a custom hook gets its own independent instance of that hook's state and effects.
  • This pattern is fundamental to writing clean, maintainable, and scalable React applications by following the DRY (Don't Repeat Yourself) principle.
  • Reusing hooks like useFetch or useCounter abstracts away complex implementation details, making your components easier to read and understand.

Next Up:

You've created a simple useCounter hook and a more complex useFetch hook (in theory). Now, we're going to build another incredibly common and useful hook. In the next lesson, we will build a useLocalStorage custom hook to persist state in the browser. This will allow your application's state to be saved even when the user closes the browser tab and comes back later