reactjsnext.js

Problem loading detail data in a Master-Detail NextJS app


I'm just starting out learning React/NextJS and need to put together a basic frontend for an API we have. To start with, I'm trying to display a list of clients then get a list of products for the selected client, using client side components and state to manage the selected items.

So far, my app looks like:

/app/page.tsx

import { Clients } from "@/components/clients";

export default async function Home() {
  return (
    <div>
      <Clients />
    </div>
  );
}

/components/clients.tsx

'use client';

import { useState } from 'react';
import { ClientList } from './clientList';
import { ProductList } from './productList';

export const Clients = () => {
    const [selectedClient, setSelectedClient] = useState(null);

    return (
        <div className="h-full flex justify-start items-start md:overflow-hidden">
            <div className="md:w-60">
                <ClientList onSelect={setSelectedClient}/>
            </div>
            <div className="flex-grow">
                <ProductList client={selectedClient} />
            </div>
        </div>
      );
}

/components/clientList.tsx

'use client';

import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { Client } from "@/app/lib/definitions";
import { getClients } from "../app/lib/data";

export const ClientList = ({ onSelect }) => {
    const [items, setItems] = useState([]);

    useEffect(() => {
        getClients().then((data) => setItems(data));
    }, []);
    
    return (
        <div>
            <div>Clients</div>
            <ul>
                {items.map((item: Client) => (
                    <li key={item.Uid} onClick={() => onSelect(item)}>{item.Name}</li>
                ))}
            </ul>
        </div>
    );
};

/components/productList.tsx

'use client';

import { useEffect, useState } from 'react';
import { getProducts } from "@/app/lib/data";
import { Client } from '@/app/lib/definitions';

export const ProductList = ({ client } : { client:Client}) => {
    const [items, setItems] = useState([]);

    if (!client) {
        return <div>Products</div>
    }
    
    useEffect(() => {
        getProducts(client.Uid).then((data) => setItems(data));
    }, []);    

    return (
        <div>
            <div>Products</div>
            <ul>
                {items.map((item) =>
                    <li key={item.Uid}>{item.Name}</li>
                )}
            </ul>
        </div>
    );
};

The data is loading from async functions using fetch. The app is loading clients ok, but if I select a client, I get the following:

React has detected a change in the order of Hooks called by ProductList. This will lead to bugs and errors if not fixed.

If I remove the null check on client in the productList component, it gets very upset about trying to access properties that don't exist on a null object.

Given I am very new to all this, I'm sure I've got some pattern or something equally simple wrong here.


Solution

  • In productList, try to pass dependency for client in useEffect,

     useEffect(() => {
      if(client)
      {
        getClients().then((data) => setItems(data));
      }
     }, [client]);
    

    also ensure that items contains some data

    <ul>        
     {items && items.map((item) =>          
     <li key={item.Uid}>
      {item.Name}
     </li>
    )}
    </ul>