Skip to content

Instantly share code, notes, and snippets.

@markerikson
Last active June 25, 2024 22:31
Show Gist options
  • Select an option

  • Save markerikson/bd9f03e0808558c5951e02f1aa98c563 to your computer and use it in GitHub Desktop.

Select an option

Save markerikson/bd9f03e0808558c5951e02f1aa98c563 to your computer and use it in GitHub Desktop.
React expandable table rows example
class ParentComponent extends Component {
constructor() {
super();
this.state = {
data : [
{id : 1, date : "2014-04-18", total : 121.0, status : "Shipped", name : "A", points: 5, percent : 50},
{id : 2, date : "2014-04-21", total : 121.0, status : "Not Shipped", name : "B", points: 10, percent: 60},
{id : 3, date : "2014-08-09", total : 121.0, status : "Not Shipped", name : "C", points: 15, percent: 70},
{id : 4, date : "2014-04-24", total : 121.0, status : "Shipped", name : "D", points: 20, percent : 80},
{id : 5, date : "2014-04-26", total : 121.0, status : "Shipped", name : "E", points: 25, percent : 90},
],
expandedRows : []
};
}
handleRowClick(rowId) {
const currentExpandedRows = this.state.expandedRows;
const isRowCurrentlyExpanded = currentExpandedRows.includes(rowId);
const newExpandedRows = isRowCurrentlyExpanded ?
currentExpandedRows.filter(id => id !== rowId) :
currentExpandedRows.concat(rowId);
this.setState({expandedRows : newExpandedRows});
}
renderItem(item) {
const clickCallback = () => this.handleRowClick(item.id);
const itemRows = [
<tr onClick={clickCallback} key={"row-data-" + item.id}>
<td>{item.date}</td>
<td>{item.total}</td>
<td>{item.status}</td>
</tr>
];
if(this.state.expandedRows.includes(item.id)) {
itemRows.push(
<tr key={"row-expanded-" + item.id}>
<td>{item.name}</td>
<td>{item.points}</td>
<td>{item.percent}</td>
</tr>
);
}
return itemRows;
}
render() {
let allItemRows = [];
this.state.data.forEach(item => {
const perItemRows = this.renderItem(item);
allItemRows = allItemRows.concat(perItemRows);
});
return (
<table>{allItemRows}</table>
);
}
}
@phen0menon

Copy link
Copy Markdown

@greysaga, you can show only one expanded row doing this way:

  constructor() {
    super();

    this.state = {
      data : 
        {id : 1, date : "2014-04-18", total : 121.0, status : "Shipped", name : "A", points: 5, percent : 50},
        {id : 2, date : "2014-04-21", total : 121.0, status : "Not Shipped", name : "B", points: 10, percent: 60},
      ],
      expandedRow: null,
    };
  }

  handleRowClick(rowId) {
    const currentExpandedRow = this.state.expandedRow;

    this.setState({ expandedRow: (rowId === currentExpandedRow) ? null : rowId })
  }

  renderItem(item) {
    const clickCallback = () => this.handleRowClick(item.id);

    const itemRows = [
      <tr key={"row-data-" + item.id}>
        <td onClick={clickCallback} key={"row-data-" + item.id}>{item.id}</td>
        <td>{item.date}</td>
        <td>{item.total}</td>
        <td>{item.status}</td>
        <td>{item.name}</td>
        <td>{item.points}</td>
        <td>{item.percent}</td>
       </tr>
     ];

    if (this.state.expandedRow === item.id) {
      itemRows.push(
        <tr>
            <td colspan={Object.keys(this.state.data[0]).length}>
              <div> {item.status} </div>
            </td>
        </tr>
      );
    }

    return itemRows;
  }

@rak16

rak16 commented May 29, 2019

Copy link
Copy Markdown

Sandbox link to play around (Bonus: semantic styling)

@rgymklogic

Copy link
Copy Markdown

awesome,thank you

@markovicivan

markovicivan commented Nov 28, 2019

Copy link
Copy Markdown

This is great! But I'm interested in how can you add a new row below the extended row on, let's say button click?

@markerikson

Copy link
Copy Markdown
Author

@ivanmarkovic2464: "just" a question of how you want to define what that should look like in your state, and having render logic that matches that. It could be as simple as inserting an item into the array after the index of the expanded row.

@markovicivan

Copy link
Copy Markdown

@ivanmarkovic2464: "just" a question of how you want to define what that should look like in your state, and having render logic that matches that. It could be as simple as inserting an item into the array after the index of the expanded row.

Well if possible I would like to have state defined just like you showed in here. But I'm struggling because I want to add multiple sub rows instead of just one and manipulate with them (by this I mean delete just the one sub row I want to). Something like this:
1 parent
subrow
subrow
subrow
...
2 parent
subrow
subrow
...

@joaoDionisioSantos

Copy link
Copy Markdown

Really good! Thanks

@auryn31

auryn31 commented Sep 30, 2020

Copy link
Copy Markdown

I used it in TypeScript today, so here is the typescript code:

import React, { useState } from 'react';

interface TableDataInterface {
    id: number,
    date: string,
    total: number,
    status: string,
    name: string,
    points: number,
    percent: number
}

const ParentComponent = (): JSX.Element => {


    const data: TableDataInterface[] = [

        { id: 1, date: "2014-04-18", total: 121.0, status: "Shipped", name: "A", points: 5, percent: 50 },
        { id: 2, date: "2014-04-21", total: 121.0, status: "Not Shipped", name: "B", points: 10, percent: 60 },
        { id: 3, date: "2014-08-09", total: 121.0, status: "Not Shipped", name: "C", points: 15, percent: 70 },
        { id: 4, date: "2014-04-24", total: 121.0, status: "Shipped", name: "D", points: 20, percent: 80 },
        { id: 5, date: "2014-04-26", total: 121.0, status: "Shipped", name: "E", points: 25, percent: 90 },
    ]

    const [expandedRows, setExpandedRows] = useState<number[]>([]);

    const handleRowClick = (rowId: number) => {
        const currentExpandedRows = expandedRows;
        const isRowCurrentlyExpanded = currentExpandedRows.includes(rowId);

        const newExpandedRows = isRowCurrentlyExpanded ?
            currentExpandedRows.filter(id => id !== rowId) :
            currentExpandedRows.concat(rowId);

        setExpandedRows(newExpandedRows);
    }

    const renderItem = (item: TableDataInterface): JSX.Element[] => {
        const clickCallback = () => handleRowClick(item.id);
        const itemRows = [
            <tr onClick={clickCallback} key={"row-data-" + item.id}>
                <td>{item.date}</td>
                <td>{item.total}</td>
                <td>{item.status}</td>
            </tr>
        ];

        if (expandedRows.includes(item.id)) {
            itemRows.push(
                <tr key={"row-expanded-" + item.id}>
                    <td>{item.name}</td>
                    <td>{item.points}</td>
                    <td>{item.percent}</td>
                </tr>
            );
        }

        return itemRows;
    }


    let allItemRows: JSX.Element[] = [];

    data.forEach(item => {
        const perItemRows = renderItem(item);
        allItemRows = allItemRows.concat(perItemRows);
    });

    return (
        <table>{allItemRows}</table>
    );

}

@abdallahragab40

Copy link
Copy Markdown

Thanks bro, you saved me

@grahamd711

Copy link
Copy Markdown

Does anyone know how to modify this so there are some rows that are not expandable? In this example, all rows are expandable. I'd like to only have certain rows expand. Thanks for any insight!

@janakprajapati2112

Copy link
Copy Markdown

This solution works for single expand only, How it's possible to expand it to multiple level?

@harpality

Copy link
Copy Markdown

Thanks @auryn31

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment