← Blog

React Native search: stop sending a request for every letter

6 min read
Watch the walkthrough on YouTube

Type shoes into a search box. If the app searches after every letter, it can send five requests before you’ve finished one word:

s → sh → sho → shoe → shoes

You probably wanted results for shoes. The earlier searches were just steps along the way.

I found the same small debounce helper in several of my React Native projects. It solves this by waiting for a pause in typing before updating the value used for search. This article builds that pattern from the beginning, with sample product names you can try without connecting a backend.

What is a search request?

React Native lets us build mobile apps with JavaScript and React. A search screen usually has two jobs: display what someone is typing, and ask for matching results.

A request is the app asking a server, another computer, for information. For example: “Find the products whose names contain shoes.”

Sending that request on every keystroke can mean doing work for words the person hasn’t finished typing. It also creates more opportunities for responses to arrive in an unexpected order. We’ll come back to that.

Wait 500 milliseconds after the last letter

Debouncing means waiting until changes stop for a chosen amount of time before doing something.

For this example, the wait is 500 milliseconds: half a second. Every new letter cancels the previous timer and starts another one.

Time       Input       What happens
0 ms       s           Start a timer
180 ms     sh          Cancel it; start another
360 ms     sho         Cancel it; start another
540 ms     shoe        Cancel it; start another
720 ms     shoes       Cancel it; start another
1220 ms    shoes       No more typing: search

That quick burst produces one search. If you pause for longer than 500 milliseconds between letters, you can still get several searches. The timer measures the pause; it doesn’t know whether you’ve finished a word. Actual timer callbacks can also run later if the JavaScript thread is busy.

Keep the input immediate

The search box needs two values:

  • query: the text on screen, updated as you type.
  • debouncedQuery: a copy that updates after the pause.

In React, state is a value a component remembers between renders. Updating state tells React to render with the new value. We use useState to keep the immediate text:

const [query, setQuery] = useState('');

The empty string is the starting value. setQuery changes it.

Keep the input connected to query. Connecting it to the delayed copy would make your typing feel broken because letters wouldn’t appear straight away. React Native’s onChangeText gives us the updated text whenever the input changes.

The reusable hook

A custom hook is a function that puts reusable React logic in one place. Create a file called useDebounce.ts:

import {useEffect, useState} from 'react';

export function useDebounce(value: string, delay = 500) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

useEffect runs the timer setup after the component renders. The [value, delay] list tells React which values the effect depends on. When either changes, React cleans up the previous effect before running it again.

setTimeout schedules the copy to update after the delay. The returned cleanup function cancels that scheduled update:

return () => clearTimeout(timer);

Without this line, typing five letters would leave five timers waiting to fire. Each one would still update the search value. Cancelling the old timer is what makes the helper wait for a pause.

React also runs cleanup when the component unmounts, meaning it is removed from the rendered app. React’s effect guide has an interactive timeout example if you want to see the setup and cleanup messages yourself.

One detail: the hook starts with the value you pass in. An initially empty input starts empty. A prefilled input starts with that text immediately; this hook doesn’t delay its initial value.

Try it in a screen

Place this SearchDemo.tsx beside the hook, then render it inside a screen in your existing React Native app. It uses a local list and logs each simulated search, so you don’t need an API key or a server.

import {useEffect, useState} from 'react';
import {Text, TextInput, View} from 'react-native';
import {useDebounce} from './useDebounce';

const products = [
  'Running shoes',
  'Walking shoes',
  'Canvas shoes',
  'Wool socks',
];

function searchProducts(query: string) {
  console.log('Simulated search:', query);
  return products.filter(product =>
    product.toLowerCase().includes(query.toLowerCase()),
  );
}

export default function SearchDemo() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);
  const [results, setResults] = useState<string[]>([]);

  useEffect(() => {
    const search = debouncedQuery.trim();

    if (!search) {
      setResults([]);
      return;
    }

    setResults(searchProducts(search));
  }, [debouncedQuery]);

  return (
    <View style={{padding: 24, gap: 16}}>
      <Text>Find your next pair</Text>
      <TextInput
        accessibilityLabel="Search products"
        placeholder="Try typing shoes"
        value={query}
        onChangeText={setQuery}
        autoCapitalize="none"
        autoCorrect={false}
        style={{borderWidth: 1, padding: 12}}
      />
      <Text>Search value: {debouncedQuery || '(empty)'}</Text>
      {results.map(product => (
        <Text key={product}>{product}</Text>
      ))}
    </View>
  );
}

Type shoes quickly and watch the log. You should see a search for the completed word after the pause. Now type sh, wait a second, then add oes: you’ll get a search for each pause.

Clearing the input updates the visible text immediately. In this small example, the old results clear when the delayed value becomes empty, up to about half a second later. A production screen may want to hide them immediately while its input is empty.

The list is local on purpose, so you can try the timing without setting up a backend. Filtering four strings is cheap; in a real app with a small local list, you would normally calculate the filtered results directly. The debounce becomes useful when each search starts work such as a network request.

What changes with a real API?

The hook only controls when a new search starts. It doesn’t stop a request that has already left the app.

Suppose a search for shoe starts, then you type shoes and start another search. If the older response arrives last, it could overwrite the newer results. Your fetching code needs to abort superseded requests where supported, or ignore their results when they’re no longer relevant. React documents the cleanup approach here.

You also need loading and error states. If your app already uses a query library, pass the delayed text into its query key and query function, and use its request lifecycle support. Keep the text input attached to the immediate value.

You might also require two characters before searching, or reuse cached results for a word someone has already entered. Try the delay on a device. I used 500 milliseconds here because the pause is easy to observe; your search screen may feel better with a shorter wait.

Check it before shipping

Try a quick burst of typing, a deliberate pause halfway through, and clearing the input. The letters should appear immediately in every case. Then unmount the component with a timer pending and confirm that the scheduled update is cleaned up. Navigating away may keep a screen mounted, depending on your navigation setup.

With a real API, test slow responses as well. A lower request count is useful, but the results still need to match the search the person expects to see.

If you’re applying this to your own screen, leave a comment with the behavior you’re seeing. For help with a React Native project, get in touch.

Comments

No account needed — your first comment creates an anonymous name and a secret key.

  1. Loading comments…