I was used to the previous Next.js architecture and I started a new project where I'm trying to use the new architecture with server and client components.
I have a page that is a race result table that needs to be rendered on the server for SEO purpose. Once the page is loaded, I want to have a filter input field to allow the user to find the athlete they are looking for.
I'm a bit confused about how to do it, and I feel like it's impossible. I tried to use the getServerSideProps
function as in the previous architecture, but that is not working on the new one.
Any idea about how to do it ? Here is what my page code actually looks like:
const RaceResultsPage = async ({ params }: { params: { raceId: string } }) => {
const result = await getResultByRaceId(+params.raceId);
return (
<>
<div>
{/* The input I want to use to filter the table*/}
<div className="relative mt-2 rounded-md shadow-sm">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<MagnifyingGlassIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="email"
name="email"
id="email"
className="block w-full rounded-md border-0 py-1.5 pl-10 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
placeholder="Filtrer par nom, prénom, numéro, club"
/>
</div>
</div>
<div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle">
<table className="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Pos.
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Bib.
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Nom
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Cat
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Temps
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Ecart
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{result.map((person) => (
<tr key={person.bib}>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{person.pos}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{person.bib}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm font-medium text-gray-900">
{person.name}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{person.catRank}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{person.totalTime}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{person.diff}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</>
);
};
I'm using Next.js 14.
If you want your page to be a server component, the only way to have a filter is to reload the page with something in the URL, a query string for example.
Still, to update the URL, you would need a client component while keeping the page itself as a server component.
For example, you can do so:
// app/SetQueryFilters.tsx
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";
export default function SetQueryFilters() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const createQueryString = useCallback(
(name: string, value: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set(name, value);
return params.toString();
},
[searchParams]
);
return (
<>
<input
type="text"
value={searchParams.get("filter") || ""}
onChange={(e) => {
router.push(pathname + "?" + createQueryString("filter", e.target.value));
}}
/>
</>
);
}
// app/page.tsx
import { Suspense } from "react";
import SetQueryFilters from "./SetQueryFilters";
const RaceResultsPage = async ({
params,
searchParams,
}: {
params: { raceId: string };
searchParams: { filter: string };
}) => {
const result = await fetch("https://jsonplaceholder.typicode.com/users");
let data = await result.json();
data = data.filter((person) => {
if (searchParams.filter) {
return person.name.toLowerCase().includes(searchParams.filter.toLowerCase());
}
return true;
});
return (
<>
<Suspense fallback={<div>Loading...</div>}>
<SetQueryFilters />
</Suspense>
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Username</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
{data.map((person) => (
<tr key={person.id}>
<td>{person.name}</td>
<td>{person.username}</td>
<td>{person.email}</td>
</tr>
))}
</tbody>
</table>
</>
);
};
export default RaceResultsPage;