Build a custom React hook with TypeScript and tests

Design and implement a typed custom React hook with effect cleanup, race-condition handling, an example component and ready-to-run tests. Designed for ChatGPT, Claude, Gemini and Microsoft Copilot. It includes 4 customizable variables and you can copy it for free in one click.

Intermediate · Code · Pending editorial review

Prompt

Act as a senior React engineer who writes reusable, well-typed custom hooks.

Context:
- What the hook must do: {{hook_purpose}}
- Data source or API contract: {{data_contract}}
- Stack: {{stack}}
- Test setup: {{test_setup}}

Before writing code, ask me about anything essential that is missing (exact response shape, error format, whether the hook also runs on the server).

Deliverables:
1. The hook's public API: name (starting with "use"), parameters, return value and why that shape (object or tuple).
2. The full implementation in TypeScript, with explicit types and no `any`.
3. Correct handling of: cleanup on unmount, race conditions between requests (AbortController or an ignore flag), stable callbacks (useCallback/useRef only where they actually matter) and loading, error and empty states.
4. A short example component that uses the hook.
5. Tests with {{test_setup}} covering the happy path, an error, a stale response that arrives late and unmounting mid-request (with fake timers if there is debouncing). If I chose "no tests", list the cases I should check by hand.
6. Limitations: what the hook does not do (caching, retries, SSR) and when a library such as TanStack Query would be the better choice.

Constraints:
- Follow the Rules of Hooks and do not use useEffect to derive state that can be computed during render.
- Do not invent endpoints or fields beyond {{data_contract}}; use [FILL IN] where information is missing.
- Comments only where the reason is not obvious.

Output format: sections with headings and one code block per file, labeled with its file name.

How to use it

  1. Describe in one sentence what the hook should solve and paste your real API contract. 2. Answer the clarifying questions before the code is generated. 3. Copy the files, run the tests and ask for targeted changes if anything fails.

Usage example

Purpose: search products as the user types, debounced at 300 ms · Contract: GET /api/products?q= returns { items, total } · Stack: React 18 + Vite · Tests: Vitest.

Sample output

Public API

useProductSearch(query, delay?) returns an object { items, total, loading, error }: easier to read than a tuple once there are four values.

useProductSearch.ts

import { useEffect, useState } from 'react';

export type Product = { id: string; name: string }; // [FILL IN: real fields]
type State = { items: Product[]; total: number; loading: boolean; error: Error | null };
const initial: State = { items: [], total: 0, loading: false, error: null };

export function useProductSearch(query: string, delay = 300): State {
  const [state, setState] = useState<State>(initial);

  useEffect(() => {
    const q = query.trim();
    if (!q) return setState(initial);
    const controller = new AbortController();
    const timer = setTimeout(async () => {
      setState((s) => ({ ...s, loading: true, error: null }));
      try {
        const res = await fetch(`/api/products?q=${encodeURIComponent(q)}`, { signal: controller.signal });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const data: { items: Product[]; total: number } = await res.json();
        setState({ ...data, loading: false, error: null });
      } catch (err) {
        if (!controller.signal.aborted) setState((s) => ({ ...s, loading: false, error: err as Error }));
      }
    }, delay);
    return () => { clearTimeout(timer); controller.abort(); };
  }, [query, delay]);

  return state;
}

The effect cleanup clears the timer and aborts the previous request, so a stale response can never overwrite the latest one…

Key facts

Category
Programming & development · React
Output type
Code
Level
Intermediate
Designed for
ChatGPT, Claude, Gemini and Microsoft Copilot
Verified in
Pending editorial verification
Variables
What the hook must do, Data or API contract, Stack and Test setup
Language
English
Price
Free, no sign-up
Updated
Nov 7, 2026

Frequently asked questions

When is a custom hook worth it instead of logic inside the component?

When the same stateful logic repeats across components, or when extracting it makes the component much easier to read and test. For short logic used once, it often is not worth it.

Why does the prompt focus on race conditions?

They are the most common bug in data-fetching hooks: a slow response from an earlier search can overwrite the latest one. Aborting or ignoring stale responses prevents it.

Does this replace TanStack Query or SWR?

No. For caching, retries, cross-tab sync or invalidation, a proven library is the better choice. The prompt tells you when to make that switch.

What is this prompt for?

Design and implement a typed custom React hook with effect cleanup, race-condition handling, an example component and ready-to-run tests.

Which AI tools does it work with?

It is designed for ChatGPT, Claude, Gemini and Microsoft Copilot. We have not verified it in these tools yet, so results may vary between them.

How do I customize the prompt?

Click “Customize” and fill in the variables (What the hook must do, Data or API contract, Stack and Test setup). The text updates instantly and you can then copy it in one click. Values are stored only in your browser.

Do I need experience to use it?

It is intermediate: it helps to know the topic so you can provide good context and review the result critically.

Is this prompt free?

Yes. Every HomeCasa Prompts prompt is free, with no sign-up or subscription. You can also use them in professional projects.

Can I trust the AI output?

Treat it as a high-quality draft: always check data, figures and important claims before publishing or making decisions.

Related prompts