Creating React custom hooks with Pieces
Discover what custom React hooks are, how to create one, and why they are essential for writing reusable components.
My React codebase used to be cluttered with repetitive state management logic. I often found myself duplicating the same lines of code across multiple components. That changed when I discovered React custom hooks.
They’re incredible for encapsulating common logic, like data fetching in a clean and maintainable way. Understanding why we use custom hooks in React is key. That’s why in this article, I'll share what I've learned about React hooks and show you how to create your own custom hooks.
Hooks at a glance
React Hooks are reusable functions that let you manage state and lifecycle events in functional components. Think of them as special JavaScript functions with built-in reactivity. They were first introduced in February 2019 as a modern solution to class components and Higher-Order Components (HOCs).
React provides several built-in hooks like useState
, useReducer
, and useEffect
, but it is also possible to create custom hooks to solve your particular use case.
How to create your first custom hook
When I created my first custom hooks in React, I learned the hard way that there are some critical rules to follow. Here’s what I discovered:
Hook Namimg: Hook names have to start with `use` (like
useCounter
)Top-level Calls: Hooks can only be called at the top level of a functional component, meaning no conditional statements or loops.
Functional Components Only: Hooks can only be called in a React functional components or other hooks — so not in regular JavaScript functions.
These are commonly referred to as the Rules of Hooks.
With these basic rules in mind, let me share a simple example from a project I was working on. Take a look at the following code snippet; I’m sure it’s something many of us have encountered in our own projects before:
This code fetches data from an API when the query changes, with a 300ms delay. It’s a commonly used pattern in React called debouncing, which helps prevent overloading your servers from rapid user input.
Since I often use this pattern in various places in my applications, it became clear that extracting this logic into a custom hook would be beneficial.
Here’s my first attempt at creating a custom hook to encapsulate this logic:
In this hook, I accept a value and a delay as input.
The value is a generic type, which means TypeScript will automatically infer its type based on what is passed in.
Meanwhile, the optional delay parameter determines how long the setTimeout
should wait before updating the value.
To utilize my newly created custom hook, I simply import it and call it like any regular JavaScript function:
Now, my components are no longer cluttered with repetitive debounce logic. I also realized I could take this a step further by creating a custom hook for the fetch request itself.
This allows me to reuse the fetching logic across multiple components:
Just look at how clean the component code appears now:
More importantly, the code inside my component is now streamlined, allowing me to focus on what I want to achieve without getting lost in implementation details.
I also discovered another hidden perk: it’s much easier to make migrations from a single location.
This approach aligns with React's best practices and emphasizes the value of utilizing custom hooks.
It's also good to note if you're building mobile applications, custom hooks work seamlessly in React Native, just as they do for React on the web.
Now that I’ve shared my approach to creating custom React hooks, let's dive deeper into common examples widely used in real-world applications.
Creating a custom data fetching hook
Data fetching is one of the most common operations in front-end development. You should fetch data from an API on page load, on a button click, or when a user scrolls to the bottom of the page. Despite the varied triggers, I've found that the underlying logic often follows a structured pattern:
Accept the API endpoint (URL) as an input parameter.
Manage and return the fetched data.
Track the status of the request, including loading, success, and error state
Typically, I would write this fetching logic directly inside our components, like so:
```typescript
I often found it tedious to repeat this logic for every endpoint I needed to fetch. Each new API call meant yet another useEffect
block with multiple useState
calls!
Not to mention that I hadn't even implemented any features for aborting ongoing fetch calls or handling refetching.
In light of this, I decided it was best to abstract this stateful logic into a custom hook. This way, I could get the data with just one line of code by invoking the hook with the appropriate URL.
Before diving into implementation, I first defined what I wanted the custom hook’s interface to look like:
Don't worry if this looks a bit overwhelming. I'll break it down for you:
The
useFetch
function accepts a URL and an optional configuration object.The configuration object primarily contains default options for the in-built
fetch
. It is of typeRequestInit
from the Fetch API.The configuration object also includes a custom
autoInvoke
property that I added to determine whether the fetch request should be made automatically.The function returns an object with the fetched
data, loading
state,error
state, and two functions:refetch
andabort.
refetch
allows you to manually trigger a fetch request.abort
lets you cancel an ongoing fetch request.
With that groundwork established, I implemented the custom hook as follows:
In the component where I needed to fetch data, the code can now be simplified to just one line.
Now, I can fetch data from as many API endpoints as I want without cluttering my UI components. This is the power of custom hooks in React.
I personally use this useFetch hook in many of my projects, and it has become a staple in my codebase. To effectively store and document precious reusable code, I save it as code snippets using Pieces.
Long-term memory helps me save, search, and enrich my code snippets across all my projects.
Whenever I save a code snippet, Pieces automatically adds meta tags, a title, and a descriptive summary explaining what the code does for easy findability. It even scans for any potential sensitive information or Personally Identifiable Information (PII).
With my code snippets organized and easily accessible, I can quickly retrieve and use them whenever I need. You can check out the custom fetch hook we just created as a snippet here.
Creating a custom hook to track the mouse position
I can recall multiple scenarios where I needed to track the mouse position inside a specific DOM element. This could be for a drag-and-drop feature, custom cursor, or other interactive use cases.
In any case, a custom hook that could return the x and y coordinates, allowing me to track the mouse movement effectively, would have been a godsend.
So, here we go—I’m going to create that hook!
Note: This is a super-simplified version just to get started.
Here is a brief explanation for the above code:
First, I create a
useState
calledposition
to hold the x and y coordinates of the mouse, initializing them to 0.I then create a
ref
usinguseRef
to allow me to attach the mouse tracking to a specific DOM element.I define the
setMousePosition
function, which serves as the event handler for mouse move events. If theref
is attached to an element, I calculate the mouse position relative to that element usinggetBoundingClientRect.
The calculations ensure that the coordinates are always non-negative and take into account window scrolling.I use
useEffect
to add an event listener for mouse movements when the component mounts. This listener callssetMousePosition
. If theref
is not attached to any element, I fall back to tracking the mouse position relative to the viewport.When the component unmounts or the
ref
changes, I clean up by removing the event listener.
Here is how I consume this hook while targeting mouse tracking on a specific <div>
element:
Here, I just call the useMouse
hook and attach the ref to the <div>
element to track the mouse position inside. The coordinates are displayed on the screen as the mouse moves within the element.
If I didn’t attach the provided ref to any DOM element, the tracked mouse position would default to being relative to the root document.
Testing custom hooks in React
It's crucial to test the code written in an application to ensure it behaves as expected across different scenarios. When it comes to custom hooks, I test them just like I would test any regular React component.
In fact, they're often easier to test because they are isolated and functionally pure (meaning they do not produce side effects).
In this section, I'll share the techniques and different tools available I use daily for writing effective tests.
To give a quick refresher, there are three main types of tests: Unit, Integration, and End-to-End (E2E) testing.
As you move from Unit to Integration to E2E, the time and resources required to write and maintain these tests increase, but so does your confidence in the application’s behavior.
Here, I'll primarily focus on unit testing.
Unit testing involves testing small, isolated pieces of code to verify that each unit functions correctly. To assert these behaviors, I typically use a testing framework like Vitest.
Meanwhile, to render custom hooks in my tests, I use React Testing Library.
If you like to learn more about different types of testing, check out 9 Types of API Testing to Ensure Performance and Security and keep an eye on the new one about unit testing llms.
To illustrate a real-world scenario for testing a custom hook, consider my application where I have a hook designed to retrieve an access token for a user on an authenticated route. For this example, let’s call it useAuthState.
The token is stored in local storage, and if it doesn’t exist when the hook is called, a new one is fetched.
Thinking through its use case, there are only three possible states:
The token is present in local storage; it should immediately return the token.
The token is absent; it should fetch a new one.
An error occurs during the fetch; it should return the error.
With this outline in mind, I can begin implementing the test cases. To speed up this process, I'll turn to Pieces Copilot to scaffold the initial test suite.
Pieces Copilot is an extension of Pieces bringing the power of AI directly into my IDE. It saves me time and effort by providing context-aware suggestions tailored to my codebase.
Here's how Iuse Pieces Copilot directly in the editor to generate test cases for the useAuthState
hook:
In this case, I asked Pieces Copilot using my current selection (the entire file) to generate unit tests for the useAuthState
hook, specifying Vitest as the testing framework and React Testing Library (RTL) for rendering. Pieces provided a series of test cases that validated the hook's behavior.
Here's the code it generated:
These tests thoroughly validate the hook's behavior across all happy paths, including negative scenarios. To run the test I use:
Upon running the test suite, all tests pass successfully.

However, I’m not done yet — my tests currently depend on an external dependency: the API fetch request.
When testing custom hooks, it's common to encounter situations where you might need to mock external dependencies as you want to control their behavior.
In this case, I’ll need to mock the fetch API to simulate both success and failure scenarios. I can use the vi.spyOn
API provided by Vitest to intercept global fetch calls and return mock values.
Thanks to Pieces long-term memory, I can easily ask it to modify my tests to mock the fetch API.
This time, I’ll use the Pieces for Developers Desktop App, where my chat history is fully preserved. This highlights how seamlessly Pieces integrates with any development workflow.

Here's the result after updating the tests:
I’ve added two helper functions: mockFetchSuccess
and mockFetchFailure
. The former resolves with the provided data, while the latter rejects with an error message.
Next, I called the mocked functions in their respective test cases and restored the original fetch function after each test to avoid any possible side effects.
With these mocks in place, our tests become deterministic. I can run them as many times as I want in any environment without worrying about external API failures. If you're looking to learn more about Unit testing in React, check out this modern guide.
Conclusion
In this article, we explored React hooks, the rules that govern them, and how they serve as a powerful tool for abstracting complex logic. Additionally, we covered how to create our own custom hooks and effectively test them using Pieces Copilot as an assistant.
Pieces can be your development companion – from saving code snippets for future use and generating test cases to chatting with the copilot to help you modify existing code, Pieces can help at every step of the way.
I hope I was able to demonstrate how custom react hooks are a game-changer. If you're looking to learn more, the React official guide is a helpful resource.
Thanks for reading!
Find some time to read these articles too:
This article was first published on August, 5th, 2022, and was improved by Emmanuel Isenah as of February 14th, 2025, to improve your experience and share the latest information.
