Material-ui: Expand/Collapse TableRow component in Materail UI

Created on 12 Jun 2016  路  19Comments  路  Source: mui-org/material-ui

Problem description

I need to expand the TableRow component to open another div containing some fields. But React throws a warning if I try to add divs in the table body.
warning: validateDOMNesting(...): <div> cannot appear as a child of <tr>. See RowComponent > TableRow > tr > div.
Required functionality is similar to ^ button from the nested list component to collapse / expand. Is there any way to customize the material-ui TableRow to expand/collapse ?

I'm adding a stripped down version of how I'm trying to add a div in the table body. I have also tried adding the <TableRow> within the <div> and that throws a warning too.

Steps to reproduce

<TableBody>
      <TableRow>
         <div>
           <TableRowColumn>1</TableRowColumn>
           <TableRowColumn>John Smith</TableRowColumn>
           <TableRowColumn>Employed</TableRowColumn>
        </div>
      </TableRow>
</TableBody>

Versions

  • Material-UI: ^0.14.4
  • React: ^0.14.7
Table v0.x

Most helpful comment

@Giedriuuzs Hi, I made an example with a reverse button which reverses the order of the rows. It works well for me. The collapsed Row is still in its expected position.
https://codesandbox.io/embed/shy-sun-ydttw?fontsize=14

All 19 comments

Just remove <div> tag from it

+1
Nothing renders if you remove div tags, had to add a single TableRowColumn and give it a width over 100%.

Additionally, the inability to nest TableRows or wrap them in a parent div makes rendering a hidden row (that will later be expanded) very painful.

Update -- my workaround was to use an expandable card as my first TableRowColumn and adjust the alignment of the rest of the Columns.

Porting CardHeader's actAsExpandable property to a TableRow would be very helpful!

Hack for anyone else who needs it:

<TableBody displayRowCheckbox={false}>
  {this.props.user.leads.map((lead, i) => (
    <TableRow key={i}>
      <TableRowColumn>
        <Card style={{boxShadow: 'none'}}>
          <CardHeader
            title={lead.brand}
            style={{paddingLeft: 0, fontWeight: 600}}
            actAsExpander={true}
          />
          <CardText expandable={true}>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit.
            Donec mattis pretium massa. Aliquam erat volutpat. Nulla facilisi.
            Donec vulputate interdum sollicitudin. Nunc lacinia auctor quam sed pellentesque.
            Aliquam dui mauris, mattis quis lacus id, pellentesque lobortis odio.
          </CardText>
        </Card>
      </TableRowColumn>
      <TableRowColumn style={{verticalAlign: 'top', height: 'auto', paddingTop: '1.4em'}}>{lead.budget}</TableRowColumn>
      <TableRowColumn style={{verticalAlign: 'top', height: 'auto', paddingTop: '1.4em'}}>{lead.eventName ? 'Event' : 'Content'}</TableRowColumn>
      <TableRowColumn style={{verticalAlign: 'top', height: 'auto', paddingTop: '1.4em'}}>{lead.firstName + ' ' + lead.lastName}</TableRowColumn>
      <TableRowColumn style={{verticalAlign: 'top', height: 'auto', paddingTop: '1.4em'}}>Archive | Start Campaign</TableRowColumn>
    </TableRow>
  ))}
</TableBody>

In case anyone is interested, in Material UI version 1 you can implement an expandable table row using the Collapse component. The documentation on Cards has an example of how to use Collapse.

Thanks @doaboa @viotti for the help. Closing this issue.

In React 16.0+ with material-ui < 1.0 you can easily use Fragments to make collapsable table rows associated with another row:

  <Fragment>
    <TableRow>
      {data.map((cell, index) => (
        <TableRowColumn key={`cell-${id}-${index}`}>
          {cell}
          {isExpandable &&
            index === data.length - 1 && (
              <div onClick={onExpand}>
                <IconButton onClick={onExpand}>
                  {isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
                </IconButton>
              </div>
            )}
        </TableRowColumn>
      ))}
    </TableRow>
    {isExpandable &&
      dataExpandable.map((row, index) => {
        return (
          <TableRow key={`expand-row-${id}-${index}`}>
            {row.map((cell, index) => (
              <TableRowColumn key={`expand-cell-${id}-${index}`}>
                {cell}
              </TableRowColumn>
            ))}
          </TableRow>
        )
      })}
  </Fragment>

@viotti Do you have a working example on how to use Collapse with TableRow on v1? I'm still getting the exact same warning on v1.

@nelsyeung I've just realized that the links on my comment above were broken. I've updated them.

Here is my implementation. The key part is props.onExpandCellClick that toggles the expanded state for the next cell, x.expanded. This state variable is connected to the in property of Collapse.

const TelemetryMonitorTableBody = pure(props => (
    <Table.TableBody>
        { props.items.map((x, i) => {
            const { timestamp, ...etc } = JSON.parse(x.payload) // eslint-disable-line no-unused-vars

            return <Table.TableRow key={ i }>
                <Table.TableCell padding='none' style={ {verticalAlign: Object.keys(etc).length == 0 ? 'inherit' : 'top'} }>
                    <IconButton disabled={ Object.keys(etc).length == 0 } style={ {transform: x.expanded ? 'rotate(0)' : 'rotate(270deg)'} } onClick={ () => props.onExpandCellClick(i) }><ExpandMore /></IconButton>
                </Table.TableCell>

                <Table.TableCell padding='none'>
                    { Object.keys(etc).length == 0 ?
                        READINGS_PL200[x.reading] :

                        <div>
                            <Typography style={ {overflow: 'hidden', textOverflow: 'ellipsis'} }>{ READINGS_PL200[x.reading] }</Typography>

                            <Collapse in={ x.expanded } transitionDuration='auto' unmountOnExit>
                                <ul style={ {margin: 0, paddingLeft: 16, paddingBottom: 16} }>
                                    { Object.entries(etc).map(([key, val], j) => {
                                        return <li key={ j }><strong>{ key }</strong>: { val === true ? 'yes' : val === false ? 'no' : val }</li>
                                    }) }
                                </ul>
                            </Collapse>
                        </div>
                    }
                </Table.TableCell>

                <Table.TableCell>{ SHORT_DATETIME_FORMATTER.format(x.date) }</Table.TableCell>
            </Table.TableRow>
        }) }
    </Table.TableBody>
))

That warning arises from the Collapse element, or any other that renders as a <div/>, being inside a <tr/> instead of a <td/>. That is an HTML validation issue, has little to do with React or even Material-UI, aside from the warning itself. In the example above, placing the Collapse in the table cell gives the feeling that the entire row is expanding, because when one cell expands the others will increase in height.

If putting the Collapse inside a cell is not a solution for you, maybe you can put multiple Collapse elements in multiple cells, and make them share state?

If I'm not mistaken, I believe the above sample will basically expand the 2nd cell of the row, which will make the other cells expanded also. It doesn't feel... native.

I ended up with something like this after reading the thread:

// ...
const styles = (theme) => createStyles({
  expandCol: {
    width: '5%'
  },
  expand: {
    transform: 'rotate(180deg)',
    transition: theme.transitions.create('transform', {
      duration: theme.transitions.duration.shortest,
    }),
    margin: 0,
    padding: 0
  },
  expandOpen: {
    transform: 'rotate(0deg)'
  },
});

class SomeRow {
  // ...
  render() {
    const expandClasses = classnames(this.props.classes.expand, {
      [this.props.classes.expandOpen]: this.state.expanded
    });

    const details = this.state.expanded ? (
      <TableRow>
        <TableCell colSpan={3}>
          <Collapse in={this.state.expanded} unmountOnExit={true}>hello</Collapse>
        </TableCell>
      </TableRow>
    ) : null;

    return (
      <React.Fragment>
        <TableRow key={key}>
          <TableCell className={this.props.classes.expandCol}>
            <IconButton
              className={expandClasses}
              onClick={this.handleExpandClick}
              aria-expanded={this.state.expanded}
              aria-label="Show more">
              <ExpandMoreIcon />
            </IconButton>
          </TableCell>
          <TableCell>{this.props.data1}</TableCell>
          <TableCell>{this.props.data2}</TableCell>
        </TableRow>
        {details}
      </React.Fragment>
    );
  }
}

Hi, for those searching a simple solution to this I just made a simple combination with collapse and table.
Here is an example:

<TableRow
    hover
    style={{cursor: 'pointer'}}
    onClick={() => this.setState({collapsedRow: !this.state.collapsedRow} )}
>
    <TableCell>some content</TableCell>
    <TableCell>some content</TableCell>
    <TableCell>some content</TableCell>
</TableRow>
<Collapse
     in={this.state.collapsedRow}
     timeout="auto"
     component={collapseComponent}
     unmountOnExit
>
     {'some content to be collapse'}
</Collapse>

and here is the collapseComponent allows the collapse working on all the width of the table :

const collapseComponent = (props) => (
    <td colSpan={3}> {/* put the number of col of your table in this field */}
        <div className={props.className}>
            {props.children}
        </div>
    </td>
)

Just put this code inside a normal Table component and it will work perfectly

@AurelReb Do you know why does the content to be collapsed does not appear gradually when in is triggered?
The row first expands entirely before displaying the components collapsed.

Same as when the collapsed component is closing, it first hides the content THEN the row collapses back. Component does not disappear gradually.

Hi @ealionel
Thank you for your question! I searched a little and found that TableCell components (as well as td) cannot be collapsed with the transition. So I changed my component to work perfectly with the transition:

<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
    <Collapse
        in={this.state.collapsedRow}
        timeout='auto'
        component={collapseComponent}
        unmountOnExit
    >
        {infos}
    </Collapse>
</TableCell>

const collapseComponent = (props) => (
    <div className={props.className}>
        {props.children}
    </div>
)

I put the TableCell component as the parent of the Collapse component, but I removed the padding top and bottom to have a height of 0 when the component is supposed to be hidden.
I hope it helped you;)

@AurelReb Code works fine until the order of rows are changed. If I order by one or another collumn the collapse/expand happens on diffrent row, not on which it should. Any ideas how to fix it?

@Giedriuuzs Hi, I made an example with a reverse button which reverses the order of the rows. It works well for me. The collapsed Row is still in its expected position.
https://codesandbox.io/embed/shy-sun-ydttw?fontsize=14

@AurelReb Thanks for the example but I have a question.
I'm not sure what collapseComponent is doing or what the purpose is because I removed it and it still works perfectly fine.

@thomastaechoi I just looked at it, and you're right. When I updated my component to fix the transition, I kept my collapseComponent function, but it's useless because Collapse component already uses 'div' as root component. Thank you, I'll update the codesandbox

i try to make table row collapse with paginnion
i wonder is ther any other way to writh it bucuse i have to use <> to make it work properly :(

` const TableBodyData=(

{(rowsPerPage>0
? data.slice(page * rowsPerPage,page * rowsPerPage+rowsPerPage)
:data
).map(row=>(
<>


{row.book_title}

{row.author}
{row.added}
{row.rating}

in={rowExpanded===row.id}
timeout="auto"
unmountOnExit
style={{paddingBottom: 15, paddingTop:15}}>
{'bla bla bla'}




)) }

  {emptyRows > 0 &&(
    <TableRow style={{ height: 53 * emptyRows }}>
      <TableCell colSpan={6}/>
    </TableRow>
  )}
</TableBody>
) `

I haven't read every one of the above comments, but the material-ui site has an example of a "Collapsible table": https://material-ui.com/components/tables/#collapsible-table.

That example is exactly what I was looking for, and I'm sure many others as well.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

darkowic picture darkowic  路  62Comments

HZooly picture HZooly  路  63Comments

kybarg picture kybarg  路  164Comments

celiao picture celiao  路  54Comments

sjstebbins picture sjstebbins  路  71Comments