Search code examples
reactjstypescriptantd

Ant Design Table - Each child in a list should have a unique "key" prop


I implemented a realtime data grid table using React Redux and SignalR. After the first item is added to table/the first dispatch happens, the following console error is logged:

Warning: Each child in a list should have a unique "key" prop. Check the render method of Body. See https://reactjs.org/link/warning-keys for more information.

I understand what the issue is but how do I fix it?

import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Table } from "antd";
import { HubConnectionState } from "redux-signalr";
import hubConnection from "../store/middlewares/signalr/signalrSlice";
import { Stock, addStock } from "../store/reducers/stockSlice";
import { RootState } from "../store";

const DataGrid = () => {
  const dispatch = useDispatch();
  const stocks = useSelector((state: RootState) => state.stock.stocks);

  useEffect(() => {
    if (hubConnection.state !== HubConnectionState.Connected) {
      hubConnection
        .start()
        .then(() => {
          console.log("Started connection via SignalR");

          hubConnection.stream("GetStockTickerStream").subscribe({
            next: async (item: Stock) => {
              console.log(item);
              dispatch(addStock(item)); // Dispatch addStock action to update Redux store
            },
            complete: () => {
              console.log("Completed");
            },
            error: (err) => {
              console.error(err);
            },
          });
        })
        .catch((err) => console.error(`Faulted: ${err.toString()}`));
    }
  }, [dispatch]);

  return (
    <Table dataSource={stocks}>
      <Table.Column title="Symbol" dataIndex="symbol" key="symbol" />
      <Table.Column title="Price" dataIndex="price" key="price" />
    </Table>
  );
};

export default DataGrid;
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

export type Stock = Readonly<{
  id: number;
  symbol: string;
  price: number;
}>;

export type StockState = Readonly<{
  stocks: Stock[];
}>;

const initialState: StockState = {
  stocks: [],
};

const stockSlice = createSlice({
  name: "stock",
  initialState: initialState,
  reducers: {
    addStock: (state, action: PayloadAction<Stock>) => {
      state.stocks = [action.payload];
    },
  },
});

export const { addStock } = stockSlice.actions;

export default stockSlice.reducer;

enter image description here


Solution

  • Key property is expected in the data

    const stocks = [
      {
        key: '1', // some key in the data
        title: 'John Brown',
        symbol: 'x,
      },
      {
        key: '2',
        title: 'John Brown',
        symbol: 'x,
      },
    ] 
    
    return (
        <Table dataSource={stocks}>
          <Table.Column title="Symbol" dataIndex="symbol" key="symbol" />
          <Table.Column title="Price" dataIndex="price" key="price" />
        </Table>
      );
    

    or you can pass a rowKey prop of type function(record): string as given in the api

    return (
        <Table 
           dataSource={stocks}
           rowKey={(record) => record.key} // some function that return a unique key 
        >
          <Table.Column title="Symbol" dataIndex="symbol" key="symbol" />
          <Table.Column title="Price" dataIndex="price" key="price" />
        </Table>
      );
    

    Add or edit a row

     // assuming PayloadAction return a single object
     addEditStock: (state, action: PayloadAction<Stock>) => {
          const updatedList = state.stocks?.map(stock => {
    
             if (stock.id === PayloadAction?.id) {
               // Create a *new* object with changes
               return { ...stock, ...PayloadAction };
             } else {
                // No changes
               return stock;
             }
    
          });
    
          state.stocks = updatedList ?? [];
     },
    

    beta docs about updating array in React

    Hope it helps