Part 84: Enhancing the Search Experience: Dynamic Data Loading in Next.js

[App] Client-Side Data Fetching

[App] Client-Side Data Fetching

In the quest to build a seamless and efficient search experience, one of the key challenges is balancing performance with functionality. Initially, we made our SearchBox component work by loading all review data in a Server Component and passing it as a prop to a Client Component. However, as our content grows, this approach can lead to inefficiencies due to the increasing size of the data passed. In this blog post, we’ll explore a more dynamic approach that fetches data on demand, providing a responsive user experience without the overhead of loading all data upfront.

The Challenge of Scaling Data

As the number of reviews grows, the array returned by getSearchableReviews becomes larger, which can negatively impact performance. Passing this large data set to the SearchBox component increases the page size, potentially slowing down the application. To address this, we need a solution that allows users to find reviews quickly without preloading all data.

On-Demand Data Fetching

Instead of loading all reviews beforehand, we can call the content management system (CMS) API only after the user enters a search query. This method ensures that only relevant reviews are fetched, optimizing both data usage and performance.

  1. State Management: We use the useState hook to manage the reviews state, initializing it as an empty array.

    //lib/reviews.js
    import { useEffect, useState } from 'react';
    import { searchReviews } from '@/lib/reviews';
    
    export default function SearchBox() {
    
    const [reviews, setReviews] = useState([]);
  2. Effect Hook for Data Fetching: We utilize the useEffect hook to trigger data fetching whenever the search query changes. By including query in the dependency array, the effect runs only when the query is updated.

      useEffect(() => {
        if (query.length > 1) {
          (async () => {
            const reviews = await searchReviews(query);
            setReviews(reviews);
          })();
        } else {
          setReviews([]);
        }
      }, [query]);
      
      
      //remove
      //  const filtered = reviews.filter((review) =>
      //  review.title.toLowerCase().includes(query.toLowerCase())
      //).slice(0, 5);
    
    //change filtered.map to reviews
     {reviews.map((review) => (
    
  3. Query-Based Fetching: We create a new function, searchReviews, to fetch reviews that match the query. This function uses the $containsi operator to perform a case-insensitive search based on the title.

    export async function searchReviews(query) {
      const { data } = await fetchReviews({
        filters: { title: { $containsi: query } },
        fields: ['slug', 'title'],
        sort: ['title'],
        pagination: { pageSize: 5 },
      });
      return data.map(({ attributes }) => ({
        slug: attributes.slug,
        title: attributes.title,
      }));
    }
  4. Optimizing Search Execution: To improve user experience, we only initiate a search if the query is longer than one character. This prevents unnecessary API calls for single-character queries.

// app/reviess/page.js

import { getReviews } from '@/lib/reviews';


//const searchableReviews = await getSearchableReviews();
//   <SearchBox reviews={searchableReviews} />
  
    <SearchBox />

Benefits and Considerations

This approach significantly reduces the data transferred between the server and client, as only relevant search results are fetched. Each API call retrieves a small, manageable amount of data, ensuring quick responses and reducing the load on the server.

However, this method also increases the number of API requests, as each keystroke can potentially trigger a new fetch. To mitigate this, implementing a "debounce" mechanism can help by delaying the API call until the user stops typing, reducing the number of requests sent to the server.

Conclusion

By fetching data dynamically based on user input, we create a more efficient and responsive search feature. This method leverages the CMS API’s filtering capabilities to deliver relevant results without the overhead of loading all data upfront. While this approach has its trade-offs, it showcases a practical solution for scalable search functionality in web applications. As we continue to refine this feature, exploring techniques like debouncing will further enhance performance and user experience. Stay tuned for more insights on optimizing client-side data handling in future posts!

Last updated