skyshell skyshell -4 years ago 165
React JSX Question

ReactCSSTransition on table rows and empty table state

I'm looking to implement a table in ReactJS with the following features:


  • initially empty

  • rows are dynamically added and removed

  • when there are no rows, an empty state (e.g. a box saying "Table empty") should be displayed

  • when a row is removed, there should be a fade out transition

  • when the first row is added, there should be no fade out transition on the empty state



I came up with two approaches using ReactCSSTransitionGroup.

1. Wrap only rows into ReactCSSTransitionGroup



Codepen: https://codepen.io/skyshell/pen/OpVwYK

Here, the table body is rendered in:

renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tbody><tr><td colSpan="2">TABLE EMPTY</td></tr></tbody>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return (
<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{rows}
</ReactCSSTransitionGroup>
);}


The issue is that the last row to be removed does not get the fade out transition before disappearing since the ReactCSSTransitionGroup is not rendered when
item.length === 0
.

2. Wrap table body into ReactCSSTransitionGroup



Codepen: https://codepen.io/skyshell/pen/RpbKVb

Here, the entire
renderTBodyContent
method is wrapped into ReactCSSTransitionGroup within the
render
method:

<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{this.renderTBodyContent()}
</ReactCSSTransitionGroup>


And the
RenderTBody
method looks like:

renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tr><td colSpan="2">TABLE EMPTY</td></tr>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return rows;}


The issue is that the empty state gets animated too.

Any suggestions on how to obtain the desired behaviour?

Thanks!

Answer Source

Thank you realseanp for your pointers. Using the low level API and TweenMax instead of CSS transitions, I came up with the following solution. First, introduce a Row component:

var Row = React.createClass({
  componentWillLeave: function(callback) {
    var el = React.findDOMNode(this);
    TweenMax.fromTo(el, 1, {opacity: 1}, {opacity: 0, onComplete: callback})
  },

  componentDidLeave: function() {
    this.props.checkTableContent();
  },

  render: function() {
    const name = this.props.name;
    return (
      <tr>
        <td>{name[0]}</td>
        <td>{name[1]}</td>
      </tr>
    );
  }
});

Then populate the table based on an isEmpty flag:

var Table = React.createClass({
  getInitialState: function() {
    return {
      items: [],
      isEmpty: true
    };
  },

  addRow: function() {
    var items = this.state.items;
    var firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
    var lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
    items.push([firstName, lastName]);
    this.setState({items: items, isEmpty: false});
  },

  removeLastRow: function() {
    var items = this.state.items;
    if (items.length != 0)  {
      items.splice(-1, 1);
      this.setState({items: items});
    }
  },

  checkTableContent: function() {
    if (this.state.items.length > 0) {
      this.setState({isEmpty: false});
    }
    else {
      this.setState({isEmpty: true});
      this.forceUpdate();
    }
  },

  renderTBodyContent: function() {
    if (this.state.isEmpty) {
      return (
        <tr><td colSpan="2">TABLE EMPTY</td></tr>
      );
    }
    var that = this;
    const rows = this.state.items.map(function(name) {
      return <Row
               key={name}
               name={name}
               checkTableContent={that.checkTableContent} />;
    });
    return rows;
  },

  render: function() {
    return (
      <div>
        <button onClick={this.addRow}>Add row</button>
        <button onClick={this.removeLastRow}>Remove row</button>
        <table>
          <thead>
            <tr>
              <th>First name</th>
              <th>Last name</th>
            </tr>
          </thead>
          <ReactTransitionGroup
            component="tbody"
            transitionName="example"
            transitionEnter={false}
            transitionLeave={true}>
            {this.renderTBodyContent()}
          </ReactTransitionGroup>
        </table>
      </div>
    );
  }
});

Codepen: https://codepen.io/skyshell/pen/yMYMmv

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download