psvj
psvj

Reputation: 8840

How to Pass Event Up Through Multiple Child Components?

I have a react.js app with structure like this:

<CalcApp /> --- root component, state is set here
  <CalcTable /> --- 1st child component
    <CalcRow /> --- 2nd child component
      - input onChange event

I want to be able to listen to an an onChange event happening inside the <-CalcRow-> component, but I am having trouble passing the event all the way up to the root component.

I have looked around the web, and stackoverflow, pretty extensively, but have not found examples of how to do this.

What is the best way to pass the event from a deeply nested child component all the way back up to the root component so that I can change state?

Here is my complete code:

var React = window.React = require('react'),
// include external components from ui folder like the exmaple blow: 
//  Timer = require("./ui/Timer"),
    Timer = require("./ui/Timer"),
    mountNode = document.getElementById("app");

var catOne =  [
    {name  : 'one', value : 5, key : 1},
    {name  : 'two', value : 2, key : 2},
    {name  : 'three', value : 3, key : 3}
      ]; 

var CalcTable = React.createClass({
  changeHandler: function(){
    console.log('ding');
    this.props.onChange();
  },
  render: function() {
    var rows = [];

    // var myVar = this.props.cat1;     

    this.props.cat1.forEach(function(item){
      rows.push(<CalcRow item={item} ref="row" key={item.key}  onChange={this.changeHandler} />);
    });
    return(
      <table>{rows}</table>
    )
  }
});

var CalcRow = React.createClass({
    changeHandler: function(e) {
      console.log('ping');
        this.props.onChange();
    },
  render: function(){
    return(
        <tr>
          <td>h</td>
          <td>{this.props.item.name}</td>
          <td><input  cat1={this.props.item} value={this.props.item.value} name={this.props.item.key}  onChange={this.changeHandler}  /></td>
        </tr>
      )
  }
});

var AddRowButton = React.createClass({ 
     handleSubmit: function(e) {
      e.preventDefault();
      this.props.onSubmit(this);
  },
  render: function(){
    return(
        <form onSubmit={this.handleSubmit}>
          <input />
          <button>Add</button>
        </form>
      )
  }
});

var SectionSummary = React.createClass({
  render: function(){
  return(
    <div className="summary">
        <div className="table-summary">
        stuff
        </div>

    </div>
    );
  }
});

var CalcApp = React.createClass({
      changeHandler: function(e) {
      console.log('bong');
    },
    getInitialState: function(){
    return {
      cat1: this.props.cat1
      };
     },
    handleSubmit: function() {
        // console.log(this.props.cat1);
        // console.log(this.props.cat1.length+1);
        var newKeyVal = this.props.cat1.length+1;
        c = this.props.cat1; 
        c = c.push({name : "four", value : 4, key : newKeyVal});
        this.setState({
        cat1:c
      });
        // console.log(this.state.cat1);
    },
  render: function() {
    return (
      <div>
          <h3>title</h3>
          <CalcTable  cat1={this.props.cat1} onChange={this.changeHandler}/>
         <div className="stuff"><p>stuff</p></div>
         <div className="stuff">
            <AddRowButton cat1={this.props.cat1} onSubmit={this.handleSubmit}/>
          </div>
            <SectionSummary />
      </div>
    );
  }
});

React.render(<CalcApp cat1={catOne}/>, mountNode);

Upvotes: 14

Views: 6020

Answers (3)

Anders Ekdahl
Anders Ekdahl

Reputation: 22933

Just a word of caution when using pub/sub: It's too easy to go overboard with events. At first, it seems to solve all problems you've ever had. Then after a while you notice that all of your code is a tangled ball of event-spaghetti.

Events are a bit like globals in that they tend to make everything better until they make everything worse. And when you end up where they make everything worse, it's hard to go back unfortunately.

So use events sparsely. Prefer passing down functions to child components so that child components can notify parents. That too is a sort of event, but more isolated and easier to follow.

You might think "Well what about Flux, that is all about events?". And it sure it, but has quite a rigid structure for how and when to send events which makes it easier to manage.

Upvotes: 6

Rosdi Kasim
Rosdi Kasim

Reputation: 25956

I use PubSubJS to communicate between my React components... that way you keep everything independent of each other and loosely coupled.

You can read more about PubSubJS here: https://github.com/mroderick/PubSubJS

To communicate, one component publish and any subscriber will get the data.

I learned about this from this blog: http://maketea.co.uk/2014/03/05/building-robust-web-apps-with-react-part-1.html#component-communication

He is using window.addEventListener but I don't recommend it since not all browser supports it, plus the PubSubJS is much easier to implement.

Upvotes: 1

Daniel Perez
Daniel Perez

Reputation: 6893

One solution can be to use a common event dispatcher.

In your parent component, you would write

var dispatcher = require('./my-dispatcher')
dispatcher.on('my.event', function (e, myArg) {
  // do some stuff here
})

and in the child

var dispatcher = require('./my-dispatcher')
dispatcher.trigger('my.event', 'my arg value')

If you do not want to implement the dispatcher yourself, there are some libraries around. I have not tried it, but for example https://github.com/mrdoob/eventdispatcher.js/ seems to do this job.

Upvotes: 1

Related Questions