Search code examples
javascriptreactjsnext.jsapp-routernextjs14

Nextjs 14 Filtered Table - Server Actions & Hydration


I have a table that looks like the below. enter image description here

The indivudual rows can be edited, by clicking edit and changing their content. What I need now is that: where it's yellow i need the filter to be. So when someone enters something like at Last Name like: "Joh" All entries that match with "Joh" should be displayed.

In a nutshell my code looks like this in the app:

    /** page.js **/
    import connection from "/lib/db";
    import {format} from "date-fns";
    import Image from "next/image";
    import ClientTableRow from "@/components/ClientTableRow";

    export default async function Home() {
    /**
     * @type {{id: number, name: string, last_name: string, phone: string, email: string, 
    created_at: string}[]}
     */
    let data = [];
    try {
        const [rows] = await connection.query('SELECT * FROM clients');
        data = rows.map(row => ({
            ...row,
            created_at: format(new Date(row.created_at), 'dd-MM-yyyy HH:mm:ss')
        }));
    } catch (error) {
        console.error('Error fetching data:', error.message);
    }

    return (
      <table className="min-w-full divide-y divide-gray-300">
                                        <thead className="bg-gray-50">
                                        <tr>
                                            {/*filter should be here*/}
                                            <td>ID</td>
                                            <td>Name</td>
                                            <td>Last name</td>
                                            <td>Phone</td>
                                            <td>Email</td>
                                            <td>Created at</td>

                                            <td></td>
                                        </tr>
                                        </thead>
                                        <tbody className="divide-y divide-gray-200 bg-white">

                                        {data.map((item, index) => (
                                            <ClientTableRow key={index} client={item}/>
                                        ))}

                                        </tbody>
                                    </table>
    );

I also have an actions.js and a ClientTableRow.js file. I don't think the code is needed.

The question: How do i update the fields dynamically without reloading the page? I know how to do this with pure frontend react, i'm not sure how to handle this with nextjs. I am assuming it might be possible with partial rehydration and server actions?


Solution

  • What you've created is a React Server Page - it is allowed be invoked async, but does not permit any interactivity/dynamic scripts. Instead, you have to make it a React Client Page. Here's a sketch - the filter thingy is a bit unclear and I think out of the scope of question on how to avoid reloading. Here's a sketch where I left the filter aspect out mostly.

    'use client'; // Now, this is not a React Server Component anymore
    import connection from '/lib/db';
    import { format } from 'date-fns';
    import ClientTableRow from '@/components/ClientTableRow';
    import { useEffect, useState } from 'react';
    
    export default function Home() { // We cannot use async now, so I removed it
      const [selectedFilter, setSelectedFilter] = useState(); // no clue how filter looks like
      const [data, setData] = useState({});
      useEffect(() => {
        async function loadData() {
          const [rows] = await connection.query('SELECT * FROM clients');
          const data = rows.map(row => ({
            ...row,
            created_at: format(new Date(row.created_at), 'dd-MM-yyyy HH:mm:ss'),
          }));
          setData(data);
        }
    
        loadData(); // We're invoking this async method in a useEffect callback, which triggers a re-rendering when data gets changed
      }, [/* We load the data once, when rendering the first time */  ]);
    
    
      return (
        <table className="min-w-full divide-y divide-gray-300">
          <thead className="bg-gray-50">
          <tr>
            {/*filter should be here*/} 
            {/* TODO: add some kind of button logic here, which on click invokes setSelectedFilter with the clicked filter*/} 
            <td>ID</td>
            <td>Name</td>
            <td>Last name</td>
            <td>Phone</td>
            <td>Email</td>
            <td>Created at</td>
    
            <td></td>
          </tr>
          </thead>
          <tbody className="divide-y divide-gray-200 bg-white">
    
          {data.filter(d => true /*TODO check whether d matches the selectedFilter*/).map((item, index) => (
            <ClientTableRow key={index} client={item} />
          ))}
    
          </tbody>
        </table>
      )
    };