Skip to content

Instantly share code, notes, and snippets.

@markerikson
Last active June 30, 2022 19:08
Show Gist options
  • Star 71 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • 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>
);
}
}
@saravanareddy
Copy link

Thank you. I was looking for something like this. Nice and clean. Pure React!

@Mukundhan-I2I
Copy link

Thankyou, this saved my time a lot.

@jackson-sandland
Copy link

Thanks. This is so simple, and it works great!

@Philip-Nunoo
Copy link

👍

@deanvanniekerk
Copy link

deanvanniekerk commented Mar 9, 2018

Great, thank you for posting this. Big help.

@flexayoung
Copy link

Great solution

@jarede
Copy link

jarede commented Jul 25, 2018

Thank you!

@Tuvaoed
Copy link

Tuvaoed commented Sep 6, 2018

Thank you so much!

@soudaburger
Copy link

Seriously... this is awesome! This was trivial to implement in my already running app. Love it!

@greysaga
Copy link

Thanks for the script, what if I would like to expand clicked row but close other? Mind to teach me how? Thanks in advance

@mohamad-mk
Copy link

tanks
realy goood

@JaosnHsieh
Copy link

awesome !! really helpful !!

@eduardlorente
Copy link

Thanks!!!
Really helpful!

@phen0menon
Copy link

@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
Copy link

rak16 commented May 29, 2019

Sandbox link to play around (Bonus: semantic styling)

@rkicoach
Copy link

rkicoach commented Oct 7, 2019

awesome,thank you

@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