Home > Back-end >  How to show particular table row detail when it is clicked (Reusable Component) in react
How to show particular table row detail when it is clicked (Reusable Component) in react

Time:07-24

I have created a reusable table component but am facing an issue showing detail for the particular row. what I was doing is if the row id is equal to a particular row id then I was trying to show the detail, but in my case for all rows details are visible.

Codesandbox : reusableTableComponent

what I tried:

const TableCustm = ({ TableHeader, dataVal, selectedRowDetail }) => {
  const [selectedTableRow, setSelectedTableRow] = useState(null);

  console.log("selectedRowDetail", selectedRowDetail);
  console.log("selectedTableRow", selectedTableRow);

  const data = dataVal.map((row) => {
    const rowData = [];
    const keys = Object.keys(row);
    keys.forEach((key, index) => {
      if (index !== 0) {
        rowData.push({
          key: TableHeader[index],
          val: row[key]
        });
      }
    });
    return (
      <>
        <tr onClick={() => setSelectedTableRow(row)}>
          {rowData.map((i) => (
            <td className="font-lato text-[14px] text-p_black font-semibold py-0">
              <div className="d-flex py-2">{i.val}</div>
            </td>
          ))}
        </tr>

// **********************detail table Row ********************
        <tr>
          <td colspan={TableHeader.length}>
            <div style={{ background: "#dcdcdc", padding: "20px" }}>
              <button className="btn btn-primary">clickme</button>
              <hr className="my-2" />

              <div className="d-flex ">row detail</div>
            </div>
          </td>
        </tr>
// *******************end detail
      </>
    );
  });
  return (
    <Table responsive borderless>
      <thead>
        <tr>
          {TableHeader.map((item) => (
            <th key={item.id} className="font-normal">
              <div className="flex py-[15px]">{item.label}</div>
            </th>
          ))}
        </tr>
      </thead>
      <tbody className="border-0">{data}</tbody>
    </Table>
  );
};

CodePudding user response:

I can see what you're trying to do. I'm by no means a react specialist, but nothing on Netflix was gripping me, so took up your challenge.

You're almost there, just a few things that are happening that are getting in your way.

I've got this working on codesandbox: https://codesandbox.io/embed/goofy-tereshkova-66p1ki?fontsize=14&hidenavigation=1&theme=dark

1) React is re-rendering the App Component when you click on the row I'm not sure the best way to get around this, but every time you click the row (even with my step 2), it will re-generate the UUID. I got around this by just hard coding the IDs, as I assume you'll have a better way for generating IDs (or will need to figure out a way to stop the reloading.

But, for now, hardcode the id so you can follow step 2

const testData = [
    {
      id: 1,

2) Your use of useState between the Parent (App) and Child (TableCustm) components. Not sure if this is intentional, but you're duplicating selectedTableRow state in both components. What I think you should do is hold the state in the parent (App) component, but pass both the state and the setState method to the child component inside of app.js like so:

 <TableCustm
    TableHeader={TableHeader}
    dataVal={dataValue}
    selectedRowDetail={selectedRow}
    setRow={setSelectedRow}
 />

So now, inside the child component (TableCustom.js) you can set the state of the selected row like so:

<tr
   onClick={(i) => {
     setRow(row.id);
   }}
>

And then, becaue you're also passing down (from the Parent to Child component) the current selected row selectedRowDetail, you can then conditionally render the row on the screen.

{row.id === selectedRowDetail && 
        <tr>
          <td colspan={TableHeader.length}>
            <div style={{ background: "#dcdcdc", padding: "20px" }}>
              <button className="btn btn-primary">clickme</button>
              <hr className="my-2" />

              <div className="d-flex ">row detail</div>
            </div>
          </td>
        </tr>
}

Also, you might want to add a conditional step when setting the state of the selected row to null, so when you click again it disappears:

 <tr
          onClick={(i) => {
            if (row.id === selectedRowDetail) {
              setRow(null);
            } else {
              setRow(row.id);
            }
          }}
        >

Hope that helps!

CodePudding user response:

When you are working with React you have to understand when to use state and when to use props.

In your scenario you have two approaches:

  1. When you want to show many details at same time, each row manage it owns state.

  2. When you want to show one detail at a time, you must likely want to the parent Table component to manage your state.

It seems you want the approach 2) show one detail at a time, so you have to show it based on the selected row with selectedTableRow === row:

import React, { useState } from "react";
import { Table } from "react-bootstrap";

const TableCustm = ({ TableHeader, dataVal, selectedRowDetail }) => {
  const [selectedTableRow, setSelectedTableRow] = useState(null);

  console.log("selectedRowDetail", selectedRowDetail);
  console.log("selectedTableRow", selectedTableRow);

  const data = dataVal.map((row) => {
    const rowData = [];
    const keys = Object.keys(row);
    keys.forEach((key, index) => {
      if (index !== 0) {
        rowData.push({
          key: TableHeader[index],
          val: row[key]
        });
      }
    });
    return (
      <>
        <tr
          onClick={() =>
            setSelectedTableRow(selectedTableRow === row ? null : row)
          }
        >
          {rowData.map((i) => (
            <td className="font-lato text-[14px] text-p_black font-semibold py-0">
              <div className="d-flex py-2">{i.val}</div>
            </td>
          ))}
        </tr>
        {selectedTableRow === row && (
          <tr>
            <td colspan={TableHeader.length}>
              <div style={{ background: "#dcdcdc", padding: "20px" }}>
                <button className="btn btn-primary">clickme</button>
                <hr className="my-2" />

                <div className="d-flex ">row detail</div>
              </div>
            </td>
          </tr>
        )}
      </>
    );
  });
  return (
    <Table responsive borderless>
      <thead>
        <tr>
          {TableHeader.map((item) => (
            <th key={item.id} className="font-normal">
              <div className="flex py-[15px]">{item.label}</div>
            </th>
          ))}
        </tr>
      </thead>
      <tbody className="border-0">{data} </tbody>
    </Table>
  );
};

export default TableCustm;

CodeSandbox: https://codesandbox.io/s/epic-mcnulty-g0hqhw?file=/src/TableCustm.js

PS: I believe your code need refactoring and I highly recommend you the Code With Mosh videos to start working with React: https://www.youtube.com/watch?v=Ke90Tje7VS0

Refactored code (not ideal yet, but better):

import React, { useState } from "react";
import { Table } from "react-bootstrap";

const TableRow = ({ data, onClickRow, showDetails }) => {
  return (
    <>
      <tr onClick={onClickRow}>
        {data.map((item, i) => (
          <td
            key={i}
            className="font-lato text-[14px] text-p_black font-semibold py-0"
          >
            <div className="d-flex py-2">{item.val}</div>
          </td>
        ))}
      </tr>
      {showDetails && (
        <tr>
          <td colSpan={data.length}>
            <div style={{ background: "#dcdcdc", padding: "20px" }}>
              <button className="btn btn-primary">clickme</button>
              <hr className="my-2" />

              <div className="d-flex ">row detail</div>
            </div>
          </td>
        </tr>
      )}
    </>
  );
};

const TableCustm2 = ({ TableHeader, dataVal }) => {
  const [selectedTableRow, setSelectedTableRow] = useState(null);

  return (
    <Table responsive borderless>
      <thead>
        <tr>
          {TableHeader.map((item) => (
            <th key={item.id} className="font-normal">
              <div className="flex py-[15px]">{item.label}</div>
            </th>
          ))}
        </tr>
      </thead>
      <tbody className="border-0">
        {dataVal.map((row, index) => (
          <TableRow
            key={index}
            data={TableHeader.map((key) => ({
              key: key.label,
              val: row[key.label]
            }))}
            onClickRow={() => {
              setSelectedTableRow(index);
            }}
            showDetails={selectedTableRow === index}
          />
        ))}
      </tbody>
    </Table>
  );
};

export default TableCustm2;
  • Related