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.
Going Infinite: The Rise and Fall of a New Tycoon
$26.21 (as of November 21, 2024 15:46 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Doppelganger: A Trip into the Mirror World
$12.99 (as of November 21, 2024 15:46 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Start with Why: How Great Leaders Inspire Everyone to Take Action
$10.49 (as of November 21, 2024 15:46 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Here’s how I implemented this client-side sorting component:
"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:
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.
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.