Implement typeahead / autocomplete / suggestions with useQuery from react-query

Viewed 626

This question is posted as self-answered; as encouraged and described here:

[...] document it in public so others (including yourself) can find it later

This is react-query specific. How can the various options of the useQuery hook best be configured to support an input suggestions functionality (also known as autocomplete and typeahead)?

Does useQuery support:

  • debouncing of the input value?
  • only sending requests if the input value has at least 3 characters?
  • caching responses for previous input values as long as the current user interaction with the text field is not over?

A naïve implementation would deal with all this in userspace (i.e. not using the library, instead implementing it all in custom code):

// [x] debouncing
// [x] at least 3 characters
// [-] caching
const [value, setValue] = useState('');
const [search, setSearch] = useState('');
const { data } = useQuery([search], () => api.getSuggestions(search));
useEffect(() =>
  {
    if (value.length > 2)
    {
      const id = setTimeout(() => setSearch(value), 500);
      return () => clearTimeout(id);
    }
  },
[value]);
1 Answers

As suggested here and here a useDebounce hook is the right approach; react-query itself does not offer a debounce option.

Suppressing requests until the input value has at least 3 characters can be achieved with the enabled option (as suggested here).

The staleTime can be tweaked so that cached previous results are still considered valid. We could use 1 minute as a heuristic for the duration of the user interaction, results older than a minute are not re-used but fetched anew if the user interaction is longer.

const [value, setValue] = useState('');
const search = useDebounce(value, 500);
const { data } = useQuery([search], () => api.getSuggestions(search),
  {
    enabled: search.length > 2,
    staleTime: 1000 * 60,
  }
);

There is a codesandbox with an example.

Alternative Cache Invalidation: We could use a longer staleTime and listen for focus lost on the input to invalidate the query.

const [value, setValue] = useState('');
const search = useDebounce(value, 500);
const { data } = useQuery(['suggestions', search], () => api.getSuggestions(search),
  {
    enabled: search.length > 2,
    staleTime: 1000 * 60 * 5,
  }
);
const queryClient = useQueryClient();

<input onBlur={() => queryClient.invalidateQueries('suggestions')} />
Related