skip to Main Content

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

4

Answers


  1. I guess the problem is not the key of your columns, but the key of your rows. Can you show the structure of your datasource?
    In the antd documentation, the dataSource is supposed to look like this:

    const dataSource = [
      {
        key: '1',
        name: 'Mike',
        age: 32,
        address: '10 Downing Street',
      },
      {
        key: '2',
        name: 'John',
        age: 42,
        address: '10 Downing Street',
      },
    ];
    

    Make sure that each row has its own key.


    Regarding your edit:
    Try to format your row data, so that it looks like this:

    {
    key: "1",
    symbol: "TESLA",
    price: 65.48
    },
    {
    key: "2",
    symbol: "TESLA",
    price: 94.61
    }
    
    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
  3. As suggested by Azzy and ActionReacto you are missing the key prop, but the problem is how to add it using selector?, so here is the way you can do it

    const stocks = useSelector((state: RootState) => state.stock.stocks.map(t:any,index:number)=>({key:index,...t}));`
    

    here instead of index ,you can use any other unique value as key like stock.id.

    Hope this will help.

    Login or Signup to reply.
  4. The problem is you’re passing key as a prop to a React component class and the key prop is reserved for loop-generated (e.g. map()) components. React is telling you that every component needs to have a unique key. This has nothing to do with the key prop in your column definition for antd. So instead of passing the key as a prop, try this:

      const columns = [
        {
          title: "Symbol",
          dataIndex: "symbol",
          key: "symbol",
        },
        {
          title: "Price",
          dataIndex: "price",
          key: "price",
        },
      ];
    
      return <Table dataSource={stocks} columns={columns} />;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search