5 - Add Search Results

Adding Search Results with Raffle’s API.

The final step is implementing Search Results, which provide users with a detailed list of results based on their query. This feature is essential for delivering comprehensive answers and guiding users to relevant resources.

Search Requests

You will use the /search endpoint to perform searches using the Raffle API. This endpoint requires three key parameters:

  • uid, which identifies the search instance or tool;
  • session-id, a unique identifier for the user session to group related searches
  • query, which represents the search term provided by the user.

Additionally, the endpoint supports optional parameters like preview and device.

  • preview set to true ensures the request is excluded from the training data and the analytic insights, making it ideal for testing your implementation. device allows you to specify the user’s device type, such as “desktop” or “mobile,” to provide device-tuned search experiences and ensure the Insights dashboard shows proper information about your users’ sessions.

Note: When implementing this endpoint, it is important to manage session IDs properly to maintain accurate data grouping and insights. During testing, always use the preview parameter to prevent test data from affecting live results. Accurately setting the device parameter can enhance the search experience by accounting for device-specific behavior.

Search Response

Search results returned by the /search endpoint include a feedback_data property. This property is designed to be used when users interact with a result. Therefore, you need to send the feedback_data to the Raffle API using the /feedback endpoint, to help Raffle improve search relevance and accuracy over time. Additionally, this feedback property provides valuable insights by tracking which results users find most useful, allowing for continuous optimization and better decision-making.

Note: The search implementation will work without you sending the feedback_data property to the /feedback. However, it is essential to the accurate functionality of the “Search Inquiries”. If feedback_data is not used, then the “Click-Through Rate” will remain 0 as not all the required information is available for calculations.

You can find more information about the /feedback endpoint in our API Reference

API Call

Here’s the function to fetch search results from the Raffle API. This function sends a GET request to the search endpoint using the user’s query and returns a list of search results.

export const fetchSearchResults = async (query) => {
  const response = await fetch(
    `https://api.raffle.ai/v2/search?uid=$YOUR_RAFFLE_UID&query=${query}&session-id=${sessionId}&preview=${preview}&device=${device}`
  );
  const data = await response.json();
  return data.results;
};

Here is the POST request to the feedback endpoint

export const sendFeedback = async (feedback_data) => {
	const response = await fetch('https://api.raffle.ai/v2/feedback', {
		method: 'POST',
		headers: { 'Content-Type': 'application/json' },
		body: JSON.stringify({ type: 'click', feedback_data }),
	})
	const data = response.json();
	return data;
}

Integration of Search API Calls

We’ll use React Query’s useMutation to fetch the search results dynamically when a search is triggered.

import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { fetchSearchResults, sendFeedback } from "./api/api";

export const Search = () => {
  const [query, setQuery] = useState("");

  // Mutation for fetching search results
  const {
    data: results = [],
    isPending: isLoadingResults,
    mutate: handleFetchResults,
  } = useMutation({
    mutationKey: ["search"],
    mutationFn: fetchSearchResults,
  });

  // Mutation for sending feedback
  const { mutate: handleSendFeedback } = useMutation({
    mutationKey: ["feedback"],
    mutationFn: sendFeedback,
  });

  // Function to handle search submission
  const handleSearch = () => {
    if (query.trim()) {
      handleFetchResults(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>Search Results</h2>
        {isLoadingResults ? (
          <p>Loading...</p>
        ) : results.length > 0 ? (
          <ul>
            {results.map((result) => (
              <li key={result.url}>
                <a href={result.url} target="_blank" rel="noopener noreferrer" onClick={() => handleSendFeedback(result.feedback_data)}>
                  <h3>{result.title}</h3>
                </a>
                <p>{result.content}</p>
              </li>
            ))}
          </ul>
        ) : (
          <p>No results available for this query.</p>
        )}
      </div>
    </div>
  );
};

Explanation

  1. fetchSearchResults: Fetches detailed search results for a given query from the API.
  2. useMutation: Dynamically fetches search results when a search is triggered.
  3. Loading State: Displays a loading indicator while the results are being fetched.
  4. Rendering Results: Displays a list of results with titles, descriptions, and clickable links.

This step completes the search flow by adding the core functionality of displaying comprehensive search results.

Final Code Implementation

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 {
  fetchSearchResults,
  sendFeedback,
  fetchSuggestions,
  fetchSummary,
  fetchTopQuestions,
} from "./api";

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

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

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

  // Mutation for fetching summary
  const {
    data: summary,
    isPending: isLoadingSummary,
    mutate: handleFetchSummary,
  } = useMutation({
    mutationKey: ["summary"],
    mutationFn: fetchSummary,
  });

  // Mutation for fetching search results
  const {
    data: results = [],
    isPending: isLoadingResults,
    mutate: handleFetchResults,
  } = useMutation({
    mutationKey: ["search"],
    mutationFn: fetchSearchResults,
  });

  // Mutation for sending feedback
  const { mutate: handleSendFeedback } = useMutation({
    mutationKey: ["feedback"],
    mutationFn: sendFeedback,
  });


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

  // Handle search action
  const handleSearch = () => {
    if (query.trim()) {
      handleFetchSummary(query.trim());
      handleFetchResults(query.trim());
    }
  };

  return (
    <div>
      <h1>Search API - React Example</h1>

      {/* Search Input */}
      <div>
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Enter your query..."
        />
        <button onClick={handleSearch}>Search</button>
      </div>

      {/* Top Questions Section */}
      <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>

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

      {/* Summary Section */}
      <div>
        <h2>Summary</h2>
        {isLoadingSummary ? (
          <p>Loading...</p>
        ) : summary ? (
          <div>
            <div dangerouslySetInnerHTML={{ __html: summary.summary }} />
            {summary.references.length > 0 && (
              <div>
                <h3>References</h3>
                <ul>
                  {summary.references.map((ref) => (
                    <li key={ref.url}>
                      <a
                        href={ref.url}
                        target="_blank"
                        rel="noopener noreferrer"
                      >
                        {ref.title}
                      </a>
                    </li>
                  ))}
                </ul>
              </div>
            )}
          </div>
        ) : (
          <p>No summary available for this query.</p>
        )}
      </div>

      {/* Search Results Section */}
      <div>
        <h2>Search Results</h2>
        {isLoadingResults ? (
          <p>Loading...</p>
        ) : results.length > 0 ? (
          <ul>
            {results.map((result) => (
              <li key={result.url}>
                <a href={result.url} target="_blank" rel="noopener noreferrer" onClick={() => handleSendFeedback(result.feedback_data)}>
                  <h3>{result.title}</h3>
                </a>
                <p>{result.content}</p>
              </li>
            ))}
          </ul>
        ) : (
          <p>No results available for this query.</p>
        )}
      </div>
    </div>
  );
};