DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

Harnessing the **`onQuery: (query: string) void`** Prop — Building a Debounced, Type‑Safe SearchBar in React 19 + TypeScript

Harnessing the ** raw `onQuery: (query: string) ⇒ void` endraw ** Prop — Building a Debounced, Type‑Safe SearchBar in React 19 + TypeScript

Harnessing the onQuery: (query: string) ⇒ void Prop — Building a Debounced, Type‑Safe SearchBar in React 19 + TypeScript

TL;DRonQuery is a callback prop that lets a parent component own what happens to the search term, while the SearchBar child owns how the term is gathered. Combine it with useEffect + setTimeout and you get an elegant, reusable, debounced search field.


1 What exactly is onQuery?

interface Props {
  placeholder?: string;
  onQuery: (query: string) => void;   // ← here
}
Enter fullscreen mode Exit fullscreen mode
  • It’s just a function—a contract that says: “Give me the latest query string, I’ll do something with it.”

Think of onQuery like onClick or onChange, except you define what fires.


2 Why not fetch inside SearchBar itself?

  • Separation of concernsSearchBar cares only about UI + user events.
  • Reusability — The parent decides whether to
    • Hit the Giphy API
    • Update URL search params
    • Trigger state in Redux / Zustand
  • Testability — Pass a jest mock and assert it’s called with "cat".

3 Dissecting the Component

import { useEffect, useState, type KeyboardEvent } from 'react'

interface Props {
  placeholder?: string;
  onQuery: (query: string) => void;   // ← here
}

export const SearchBar = ({ placeholder = 'Buscar', onQuery }: Props) => {
  const [query, setQuery] = useState('');

  // 1️⃣ Debounce side‑effect
  useEffect(() => {
    const id = setTimeout(() => onQuery(query), 700);
    return () => clearTimeout(id);
  }, [query, onQuery]);

  const handleSearch = () => onQuery(query);          // 2️⃣ Immediate search
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => { // 3️⃣ Enter key
    if (e.key === 'Enter') handleSearch();
  };

  return (
    <div className="search-container">
      <input
        type="text"
        placeholder={placeholder}
        value={query}
        onChange={e => setQuery(e.target.value)}
        onKeyDown={handleKeyDown}
      />
      <button onClick={handleSearch}>Buscar</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

3.1 Debounce with useEffect

Render cycle What happens
User types “c” query → "c" → schedules timer #1
User types “ca” Clears timer #1, schedules timer #2
Pause ≥ 700 ms Timer fires → onQuery("cat")

3.2 Immediate search

Click or Enter bypass the debounce for instant feedback.


4 Parent‑Side Usage

export function GifApp() {
  const [results, setResults] = useState<Gif[]>([]);

  // Stable callback prevents needless re‑effects
  const handleQuery = useCallback((q: string) => {
    fetchGifs(q).then(setResults);
  }, []);

  return (
    <>
      <SearchBar onQuery={handleQuery} />
      <GifList gifs={results} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

React 19 tip: Wrapping handleQuery in useCallback keeps its reference stable so SearchBar’s effect only reruns when query changes.


5 Design Patterns & Best Practices

Pattern Why it rules
Lift state up UI remains dumb; parent decides side‑effects
Stable callbacks useCallback prevents re‑debounce storms
Effect cleanup Avoid memory leaks when component unmounts
Typed callback props Compile‑time guarantee of correct usage
Trim empty queries if (!q.trim()) return; inside handler

6 Extending the Pattern

6.1 Result count indicator

<SearchBar count={results.length} onQuery={handleQuery} />
Enter fullscreen mode Exit fullscreen mode

6.2 onClear hook

interface Props {
  onQuery: (q: string) => void;
  onClear?: () => void;
}
Enter fullscreen mode Exit fullscreen mode

6.3 Smooth UX with useTransition

const [isPending, start] = useTransition();
const handleQuery = (q: string) =>
  start(() => setSearch(q));  // typing stays snappy
Enter fullscreen mode Exit fullscreen mode

7 Cheat‑Sheet

Need Code / Concept
Debounce setTimeout + cleanup
Keep callback stable useCallback
Trigger on Enter onKeyDown + event.key === 'Enter'
Avoid first empty call Initialise state as ''
Cancel fetch AbortController inside useEffect

Final Thoughts

onQuery turns a tiny input into a reusable query engine nozzle.

Master typed callback props, debounced effects, and controlled inputs to build search UIs that feel instant and stay maintainable.

Happy querying — and may your API responses be ever in your favour! 🚀

Top comments (0)