Client-Side Sorting with Server-Side Search Parameters in Next.js 14

One of the powerful features of Next.js 14 is its ability to blend client and server components seamlessly. As I’ve been working with Next.js 14, I’ve encountered scenarios where sorting data on a page based on user input becomes necessary. In particular, if you’re building a grid-based UI and want to allow users to sort data on the client side, but also need to fetch sorted data from the server, it’s crucial to handle search parameters correctly.

Thank me by sharing on Twitter 🙏

Implementing Client-Side Table Sorting

In this post, I’ll walk you through how I implemented client-side sorting, supporting both sort fields and sort direction, while passing these parameters to a server component in Next.js 14. The ability to cleanly manage search parameters in Next.js 14 gives us the flexibility we need for complex UI interactions like sorting.

Why Sorting with Search Parameters Matters

When you have a page displaying a grid of data, users often want the ability to sort by various fields, such as name, date, or price. In some cases, sorting can be handled entirely on the client side, but when your data set is large or comes from a database, it’s better to fetch sorted data directly from the server.

In this setup, we’ll allow users to select both the sort field and direction (ascending or descending), and use Next.js’s search parameters (searchParams) to handle these sorting options in a type-safe, clean way. By passing these parameters to the server component, we ensure that only the relevant data is fetched, keeping our page responsive and efficient.

Step 1: Client-Side Sorting Component

The first step is to create a client-side component where users can select how they want to sort the data. We need two dropdowns: one for selecting the field (e.g., name, date, or price) and another for selecting the sort direction (ascending or descending). Whenever the user changes either of these, the component will update the URL query parameters to reflect their choice.

Here’s how I implemented this client-side sorting component:

TypeScript
"use client";

import { useState } from "react";
import { useRouter } from "next/router";

type SortOption = "name" | "date" | "price";
type SortDirection = "asc" | "desc";

const GridSortSelector = () => {
  const [sort, setSort] = useState<SortOption>("name");
  const [direction, setDirection] = useState<SortDirection>("asc");
  const router = useRouter();

  const handleSortChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const selectedSort = e.target.value as SortOption;
    setSort(selectedSort);

    // Update the URL with sort and direction
    router.push(`?sort=${selectedSort}&direction=${direction}`);
  };

  const handleDirectionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const selectedDirection = e.target.value as SortDirection;
    setDirection(selectedDirection);

    // Update the URL with sort and direction
    router.push(`?sort=${sort}&direction=${selectedDirection}`);
  };

  return (
    <div>
      <label htmlFor="sort">Sort By:</label>
      <select id="sort" value={sort} onChange={handleSortChange}>
        <option value="name">Name</option>
        <option value="date">Date</option>
        <option value="price">Price</option>
      </select>

      <label htmlFor="direction">Direction:</label>
      <select id="direction" value={direction} onChange={handleDirectionChange}>
        <option value="asc">Ascending</option>
        <option value="desc">Descending</option>
      </select>
    </div>
  );
};

export default GridSortSelector;

The GridSortSelector component is straightforward. When the user selects a sort field or direction, the URL is updated with the appropriate query parameters. This way, we ensure that the client-side sorting interacts with the rest of the application seamlessly.

Step 2: Passing Search Parameters to the Server Component

Next.js 14 introduces a handy way to pass search parameters from the URL directly into server components using the searchParams object. These parameters allow us to dynamically fetch data from the server based on the user’s sorting choices.

To pass the sorting data to the server, we need to update the server component to accept searchParams as a prop. Here’s how I structured this part:

TypeScript
type SearchParams = { [key: string]: string | undefined };

type GridServerComponentProps = {
  searchParams?: SearchParams;
};

const GridServerComponent = async ({ searchParams }: GridServerComponentProps) => {
  const sort = searchParams?.sort || "name"; // Default to 'name'
  const direction = searchParams?.direction || "asc"; // Default to 'asc'

  // Fetch sorted data with sort field and direction
  const items = await fetchSortedData(sort, direction);

  return (
    <div className="grid">
      {items.map((item) => (
        <div key={item.id} className="grid-item">
          <h3>{item.name}</h3>
          <p>Date: {item.date}</p>
          <p>Price: {item.price}</p>
        </div>
      ))}
    </div>
  );
};

export default GridServerComponent;

The GridServerComponent accepts searchParams and uses them to fetch sorted data from the server. This way, only the relevant data is sent to the client, minimizing unnecessary data transfer and improving performance.

Step 3: Fetching Sorted Data

Finally, the server-side logic needs to handle sorting based on the provided parameters. I wrote a helper function, fetchSortedData, that sorts the data based on the sort field and direction values.

TypeScript
export async function fetchSortedData(sort: string, direction: string) {
  const data = [
    { id: "1", name: "Item A", date: "2023-09-01", price: 20 },
    { id: "2", name: "Item B", date: "2023-09-02", price: 10 },
    { id: "3", name: "Item C", date: "2023-09-03", price: 30 },
  ];

  const sortedData = data.sort((a, b) => {
    let comparison = 0;

    if (sort === "name") {
      comparison = a.name.localeCompare(b.name);
    } else if (sort === "date") {
      comparison = new Date(a.date).getTime() - new Date(b.date).getTime();
    } else if (sort === "price") {
      comparison = a.price - b.price;
    }

    return direction === "desc" ? -comparison : comparison;
  });

  return sortedData;
}

This function takes the sort field and direction as arguments and sorts the data accordingly. Whether it’s sorting by name, date, or price, the function returns the correct order of items for display.

Conclusion

With this approach, I’ve found that it’s easy to implement a robust, user-friendly sorting system in Next.js 14. By combining client-side components to manage user input and passing search parameters to server components, we can handle complex data interactions cleanly and efficiently. Next.js 14’s support for handling search parameters in server components adds a level of flexibility that makes this process even smoother.

By integrating these techniques, you can maintain a responsive, dynamic interface for your users without sacrificing performance, all while leveraging the power of Next.js’s latest features.

Share this:

Leave a Reply