Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand/Collapse TableRow component in Materail UI #4476

Closed
shiveeg1 opened this issue Jun 12, 2016 · 19 comments
Closed

Expand/Collapse TableRow component in Materail UI #4476

shiveeg1 opened this issue Jun 12, 2016 · 19 comments
Labels
component: table This is the name of the generic UI component, not the React module! v0.x

Comments

@shiveeg1
Copy link

shiveeg1 commented Jun 12, 2016

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

Just remove <div> tag from it

@mpontikes mpontikes mentioned this issue Aug 5, 2016
13 tasks
@oliviertassinari oliviertassinari added the component: table This is the name of the generic UI component, not the React module! label Feb 12, 2017
@doaboa
Copy link
Contributor

doaboa commented Feb 15, 2017

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

@doaboa
Copy link
Contributor

doaboa commented Feb 15, 2017

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>

@viotti
Copy link
Contributor

viotti commented May 29, 2017

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.

@shiveeg1
Copy link
Author

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

@mateuszroth
Copy link

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>

@nelsyeung
Copy link

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

@viotti
Copy link
Contributor

viotti commented Jan 28, 2018

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

@amimas
Copy link

amimas commented Jul 20, 2018

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.

@ccarrasc
Copy link

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>
    );
  }
}

@AurelReb
Copy link

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

@ealionel
Copy link

ealionel commented Apr 30, 2019

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

@AurelReb
Copy link

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

@Giedriuuzs
Copy link

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

@AurelReb
Copy link

@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

@thomastaechoi
Copy link

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

@AurelReb
Copy link

AurelReb commented Jul 9, 2019

@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

@leehep
Copy link

leehep commented Mar 29, 2020

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=>(
<>
<TableRow onClick={()=>handleExpandedChange(row.id)} key={row.id} scope="row">

{row.book_title}

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


<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={4}>
<Collapse
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>
) `

@cewerling
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: table This is the name of the generic UI component, not the React module! v0.x
Projects
None yet
Development

No branches or pull requests