3 - Add Autocomplete

Adding Autocomplete with Raffle’s API.

In this step, we’ll implement Suggestions (Autocomplete) to offer real-time guidance as users type their queries. Suggestions help improve the user experience by showing relevant query options dynamically.

To retrieve suggestions, use the Raffle API’s /autocomplete endpoint. This requires a uid and a query parameter, with an optional limit parameter to control the number of suggestions returned.

Keep in mind that suggestions are generated after a certain number of questions have been asked. If the User Interface (UI) is new, there may be no suggestions available initially. We recommend implementing the logic once and letting the API handle the rest. A good approach is to display suggestions if they are available; otherwise, default to showing top questions.

Why Use a Debounced Value?

Fetching suggestions directly on every keystroke can overwhelm the API and negatively impact performance. To address this, we use a debounced value, which triggers the API call only after the user has paused typing for a short time. This ensures efficient use of resources.

We’ll use the useDebounce hook from the @uidotdev/usehooks package to simplify the implementation.

API Call

Here’s the function to fetch suggestions from the Raffle API:

export const fetchSuggestions = async (query, limit) => {
  const response = await fetch(
    `https://api.raffle.ai/v2/autocomplete?uid=$YOUR_RAFFLE_UID&query=${query}&limit=${limit}`
  );
  const data = await response.json();
  return data.suggestions;
};

This function sends a GET request to the autocomplete endpoint using the user’s query and returns a list of suggestions.

Integration with React

We’ll integrate the suggestions feature into the UI using React Query’s useMutation and the useDebounce hook for clean and efficient state management.

import { useEffect, useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { useDebounce } from "@uidotdev/usehooks";
import { fetchSuggestions } from "./api";

export const Search = () => {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 500); // Debounce with 500ms delay

  const { data: suggestions = [], mutate: handleFetchSuggestions } =
    useMutation({
      mutationKey: ["suggestions", debouncedQuery],
      mutationFn: fetchSuggestions,
    });

  // Trigger suggestions when debouncedQuery is at least 3 characters long
  useEffect(() => {
    if (debouncedQuery.length >= 3) {
      handleFetchSuggestions(debouncedQuery);
    }
  }, [debouncedQuery, handleFetchSuggestions]);

  const handleSearch = () => {
    if (query.trim()) {
      console.log("Search triggered with query:", query.trim());
    }
  };

  return (
    <div>
      <h2>Suggestions</h2>
      <ul>
        {suggestions.map(({ suggestion }, index) => (
          <li
            key={index}
            onClick={() => {
              setQuery(suggestion);
              handleSearch();
            }}
          >
            {suggestion}
          </li>
        ))}
      </ul>
    </div>
  );
};

Explanation

  1. fetchSuggestions: Fetches real-time suggestions from the /autocomplete endpoint.
  2. useDebounce: Prevents API calls on every keystroke by debouncing the input value.
  3. Minimum Query Length: Ensures that suggestions are fetched only when the query is at least 3 characters long.
  4. Rendering Suggestions: Displays suggestions in a list and allows users to click on them to populate the search field.

By implementing debouncing and a character limit, this approach optimizes the API usage and ensures a smooth user experience.

Total Code

At this stage, your full code should look like this:

import { useEffect, useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useDebounce } from "@uidotdev/usehooks";
import { fetchSuggestions, fetchTopQuestions } from "./api";

export const Search = () => {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 500); // Debounce with 500ms delay

  const { data: topQuestions = [], isLoading } = useQuery({
    queryKey: ["topQuestions"],
    queryFn: fetchTopQuestions,
  });

  const { data: suggestions = [], mutate: handleFetchSuggestions } =
    useMutation({
      mutationKey: ["suggestions", debouncedQuery],
      mutationFn: fetchSuggestions,
    });

  // Trigger suggestions when debouncedQuery is at least 3 characters long
  useEffect(() => {
    if (debouncedQuery.length >= 3) {
      handleFetchSuggestions(debouncedQuery);
    }
  }, [debouncedQuery, handleFetchSuggestions]);

  const handleSearch = () => {
    if (query.trim()) {
      console.log("Search triggered with query:", query.trim());
    }
  };

  return (
    <div>
      <h1>Search API - React Example</h1>
      <div>
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Enter your query..."
        />
        <button onClick={handleSearch}>Search</button>
      </div>

      <div>
        <h2>Top Questions</h2>
        {isLoading ? (
          <p>Loading top questions...</p>
        ) : (
          <ul>
            {topQuestions.map(({ question }) => (
              <li key={question} onClick={() => setQuery(question)}>
                {question}
              </li>
            ))}
          </ul>
        )}
      </div>

      <div>
        <h2>Suggestions</h2>
        <ul>
          {suggestions.map(({ suggestion }, index) => (
            <li
              key={index}
              onClick={() => {
                setQuery(suggestion);
                handleSearch();
              }}
            >
              {suggestion}
            </li>
          ))}
        </ul>
      </div>

      <div>
        <h2>Summary</h2>
        <p>Summary will appear here...</p>
      </div>

      <div>
        <h2>Search Results</h2>
        <p>Search results will appear here...</p>
      </div>
    </div>
  );
};