How can I communicate between adjacent elements in ReactJS?

124 Views Asked by At

I'm trying to construct a table in ReactJS that generates two rows for each element in an array. The problem I'm having trouble solving is generating them in such a way where row(n) can send a message to row(n+1).

The application of this is opening a detail view if one of the rows is clicked.

Right now my approach is to generate the rows and pass row(n+1) as a prop of row.

const orders = [
  // this is just some example data
  {
   "name": "lorem",
   "number": "20.00",
   "price": "20.00",
   "image": "http://localhost/path/to/image1.jpg"
  },
  {
   "name": "lorem",
   "number": "20.00",
   "price": "20.00",
   "image": "http://localhost/path/to/image1.jpg"
  },
];

const Orders = React.createClass({

  renderAllRows(order) {
    // this function would generate all the rows of the table
    const rows = [];
    orders.map(function (order, index) {
      const OrderDetailInstance = <OrderDetail display={false}  item={order} />
      // OrderDetailInstance is passed as a prop of OrderItemInstance
      const OrderItemInstance = <OrderItem detail={OrderDetailInstance} item={order}/>;
      rows.push(OrderItemInstance, OrderDetailInstance);
    });
    return rows;
  },

  render() {
    const { state } = this;
    const { orders } = state;
    const { isLastPage } = state;

    return (
        <Table>
          <tbody>
            {this.renderAllRows(orders).map(function(row) {
               return row;
            })}
          </tbody>
        </Table>
    );
  },
});

However this doesn't work, because while the prop is successfully passes, I do not know how to access methods on a react element. So I'm obviously going about this wrong.

Currently this is my unsuccessful approach to calling a method on a react element.

const OrderItem = React.createClass({
  render() {
    const item = this.props.item;


    return (
      <tr>
        <td>{item.number}</td>
        <td>{item.number}</td>
        <td>
          <a onClick={this.openOrderDetail}>open detail</a>
        </td>
      </tr>
    );
  },

  openOrderDetail() {
    // This is where I'm trying to call the prop's method.
    this.props.detail.open();
  }
});

const OrderDetail = React.createClass({
  render() {
    const display = this.props.display;
    const classes = display ? classNames('') : classNames('hidden');
    return (
      <tr className={classes}>
        <td colSpan="3">
          <div>
            This is the detail of the previous row
          </div>
        </td>
      </tr>
    );
  },

  open() {
    // This should IDEALLY be exposed to any react element that has
    // OrderDetail as a prop.
    console.log("open");
  }
});

I'm open to the idea of using the state of the Orders class but I can't help feel that would be overkill.

2

There are 2 best solutions below

1
On

What if you just used CSS to show/not show the detail row unless it was preceded by a row with a certain class.

So, your DOM could look like

<table>
  <!-- Item -->
  <tr class='show-detail'></tr>
  <!-- Detail -->
  <tr class='detail-row'></tr>
</table>

And your CSS could be something like

.detail-row { display: none}
.show-detail + .detail-row { display: block }

So a detail-row is not shown, unless it immediately follows a show-detail item.

Then, your OrderItem component is only worried about toggling a class on itself, rather than talking to it's sibling.

0
On

Wrap OrderItem and OrderDetail in a container component, say: OrderView. Keep the state of whether to show the detail or not in the new OrderView. In your OrderView.render(), choose to show the detail row or not based on the state. Instead of trying to call open(), just set the OrderView.state as required.

This tutorial really helped me a lot.