Skip to content

Instantly share code, notes, and snippets.

@markerikson
Last active June 30, 2022 19:08
Show Gist options
  • Save markerikson/bd9f03e0808558c5951e02f1aa98c563 to your computer and use it in GitHub Desktop.
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>
);
}
}
@markovicivan
Copy link

markovicivan commented Nov 28, 2019

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
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

@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

Really good! Thanks

@auryn31
Copy link

auryn31 commented Sep 30, 2020

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

Thanks bro, you saved me

@grahamd711
Copy link

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

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

@harpality
Copy link

Thanks @auryn31

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