How to communicate between React components

February 12th, 2015 | javascript, react |

If you are not yet confortable with the notion of ownership and parent/child in React, take a look at my previous post.

How to communicate between React components ? That’s a good question, and there are multiple answers. That depends of the relationship between the components, and then, that depends on what you prefer.

I am not talking about data-stores, data-adapters or this kind of data-helpers that gets data from somewhere that you need to dispatch to your components, I’m really just talking about communication between React components only.

 

There are 3 possible relationships:

How an owner can talk to its ownee

That the easiest case actually, very natural in the React world and you are already using it.
You have a component that renders another one and passing it some props.

var MyContainer = React.createClass({
    getInitialState: function() {
        return { checked: true };
    },
    render: function() {
        return  ;
    }
});
var ToggleButton = React.createClass({
    render: function() {
        return ;
    }
});

Here renders a passing it a checked property. That’s communication.

You just have to pass a prop to the child component your parent is rendering.

By the way, in my example, notice that clicking on the checkbox has no effect:

Warning: You provided a checked prop to a form field without an onChange handler.
This will render a read-only field. If the field should be mutable use defaultChecked.
Otherwise, set either onChange or readOnly. Check the render method of ToggleButton.

The has its this.props set by it parent, and only by it (and they are immutable).

Hierarchy problem

One disavantage of this technique is if you want to pass down a prop to a grandson (you have a hierarchy of components): the son has to handle it first, then pass it to the grandson. So with a more complex hierarchy, it’s going to be impossible to maintain. Example with just one intermediate :

var MyContainer = React.createClass({
    render: function() {
        return  ;
    }
});

var Intermediate = React.createClass({
  render: function() {
    // Intermediate doesn't care of "text", but it has to pass it down nonetheless
    return ;
  }
});

var Child = React.createClass({
    render: function() {
        return {this.props.text};
    }
});

A solution exists to avoid that and automatically have the parent talking to its grandson without the child to pass properties manually, but it is still not officially fully supported by the React team: the context.

Context

Update 2015-09-22 : I thought since a while that I was missing this part, fixed now!

To solve this problem of hierarchy, a solution exists, the context.

The context is something that is still undocumented on the React documentation but that everybody is already using since a while, because of it so useful. But the team has still work to do that could break the current behavior, hence the undocumented part. Use it at your own risks keeping in mind that, one day, a release will potentially breaks the compatibility, and you’ll need to rewrite those parts.

The context is very useful when you have a dynamic tree of components, where things are moving, where you have a lot of properties to pass down : passing explicitely every props to every children is clearly not an option.

The concept of context is easy to understand:
– one component makes a JS object at disposal that will be available for _any_ of its children components, grand children and so on, without them to being passed down explicitely.
– any child can use this.context to access this object (passed behind the scene by React)

But, to use it, you have to follow some rules :
– define getChildContext on the parent to return what is the context (any JS object)
– define childContextTypes on the parent to define what is the type of each property in this context (React.PropTypes.)
– define contextTypes on a (child) component that want to read from its context (which property does it want to read)
This will allow React to check the properties are properly exposed on runtime (dev only).

If we take back our previous example, but using context this time, we can avoid to modify the component but still have reading props from :

var MyContainer = React.createClass({
  getChildContext: function() {
    // it exposes one property "text", any of the components that are
    // rendered inside it will be able to access it
    return { text: 'Where is my son?' };
  },
  // we declare text is a string
  childContextTypes: {
    text: React.PropTypes.string
  },
  render: function() {
    // no props to pass down
    return  ;
  }
});

var Intermediate = React.createClass({
  render: function() {
    // this component has no props
    return ;
  }
});

var Child = React.createClass({
  // we declare we want to read the .text property of the context
  contextTypes: {
    text: React.PropTypes.string
  },
  render: function() {
    // this component has access to the current context
    // exposed by any of its parent
    return {this.context.text};
  }
});

You can see this code in action on this jsbin.

But even if that works, try to be sure there is no other way and that is the best way for you.
Just as a side node, react-router is using it internally.

How a ownee can talk to its owner

Now, let’s say the controls its own state and wants to tell its parent it has been clicked, for the parent to display something. Thus, we add our initial state and we add an event handler on the change event of our input:

var ToggleButton = React.createClass({
  getInitialState: function() {
    return { checked: true };
  },
  onTextChanged: function() {
    console.log(this.state.checked); // it is ALWAYS true
  },
  render: function() {
    return ;
  }
});

Notice that because I don’t change the state of this.state.checked in onTextChanged, the value is always true. React doesn’t handle the toggle of your own value just because it’s a input checkbox, it just notify you but nothing truly changed.

Therefore we add the state change and this is where we would like to callback our parent right ?

  onTextChanged: function() {
    this.setState({ checked: !this.state.checked });
    // callbackParent(); // ??
  },

To have a reference to a callback pointing to the parent, we are simply going to use the first way we talked about : owner to ownee (parent to child) communication. The parent will pass a callback through a prop: we can pass anything through them, they are not DOM attributes, they are pure Javascript object.

Here is an example where the notify its owner its state changed. The parent listens to this event and change its own state too to adapt its message :

var MyContainer = React.createClass({
    getInitialState: function() {
        return { checked: false };
    },
    onChildChanged: function(newState) {
        this.setState({ checked: newState });
    },
    render: function() {
        return  
Are you checked ? {this.state.checked ? 'yes' : 'no'}
; } }); var ToggleButton = React.createClass({ getInitialState: function() { // we ONLY set the initial state from the props return { checked: this.props.initialChecked }; }, onTextChanged: function() { var newState = !this.state.checked; this.setState({ checked: newState }); this.props.callbackParent(newState); // hey parent, I've changed! }, render: function() { return ; } });

And the compiled version of the where we can see the different props passed by in a classic JS object:

return  React.createElement("div", null, 
          React.createElement("div", null, "Are you checked ? ", this.state.checked ? 'yes' : 'no'), 
          React.createElement(ToggleButton, {text: "Toggle me", initialChecked: this.state.checked, callbackParent: this.onChildChanged})
        );

Here is the result :

When I click on the input, the parent gets notified, and changes its message to ‘yes’.

We have the same problem than before : if you have intermediate components in-between, you have to pass your callback through the props of all of the intermediate components to get to your target.

More details about the React event system

In the event handler onChange and any other React events, you will have access to :

  • this: this is your component
  • one argument, which is the event from React : a SyntheticEvent. Here is what it looks like :

All events managed by React have nothing to do with the default javascript onclick/onchange we used to know. React has its own implementation. Basically, they bind every events on the body with a selector à la jQuery :

document.on('change', 'input[data-reactid=".0.2"]', function() { ... })

This code is not from React, it’s just an example to explain how they bind every events to the document.

If I’m not mistaken, the React code that truly handle the events is that :

var listenTo = ReactBrowserEventEmitter.listenTo;
...
function putListener(id, registrationName, listener, transaction) {
...
  var container = ReactMount.findReactContainerForID(id);
  if (container) {
    var doc = container.nodeType === ELEMENT_NODE_TYPE ?
      container.ownerDocument :
      container;
    listenTo(registrationName, doc);
}
...
// at the very of end of the listenTo inner functions, we can find:
target.addEventListener(eventType, callback, false);

Here is the full list of the events React supports.

Using the same callback

Just for fun, let’s try with multiple and display the sum of checked input in the container :

var MyContainer = React.createClass({
    getInitialState: function() {
        return { checked: false, totalChecked: 0 };
    },
    onChildChanged: function(newState) {
      // if newState is true, it means a checkbox has been checked.
      var newTotal = this.state.totalChecked + (newState ? 1 : -1);
      this.setState({ totalChecked: newTotal });
    },
    render: function() {
        return  
How many are checked ? {this.state.totalChecked}
; } }); var ToggleButton = React.createClass({ getInitialState: function() { return { checked: this.props.initialChecked }; }, onTextChanged: function() { var newState = !this.state.checked; this.setState({ checked: newState }); this.props.callbackParent(newState); // hey parent, I've changed! }, render: function() { return ; } });

That was pretty easy to do that right ? We just added totalChecked instead of checked on and update it when a child changes. The callback we pass is the same for every .

Help me, my components are not related!

The only way if your components are not related (or are related but too further such as a grand grand grand son and you don’t want to mess with the intermediate components) is to have some kind of signal that one component subscribes to, and the other writes into. Those are the 2 basic operations of any event system: subscribe/listen to an event to be notify, and send/trigger/publish/dispatch a event to notify the ones who wants.

There are at least 3 patterns to do that. You can find a comparison here.

Here is a quick recap:

  • Event Emitter/Target/Dispatcher : the listeners need to reference the source to subscribe.
    • to subscribe : otherObject.addEventListener(‘click’, function() { alert(‘click!’); });
    • to dispatch : this.dispatchEvent(‘click’);
       
  • Publish / Subscribe : you don’t need a specific reference to the source that triggers the event, there is a global object accessible everywhere that handles all the events.
    • to subscribe : globalBroadcaster.subscribe(‘click’, function() { alert(‘click!’); });
    • to dispatch : globalBroadcaster.publish(‘click’);
       
  • Signals : similar to Event Emitter/Target/Dispatcher but you don’t use any random strings here. Each object that could emit events needs to have a specific property with that name. This way, you know exactly what events can an object emit.
    • to subscribe : otherObject.clicked.add(function() { alert(‘click’); });
    • to dispatch : this.clicked.dispatch();

You can use a very simple system if you want, with no more option, it’s quite easy to write :

// just extend this object to have access to this.subscribe and this.dispatch
var EventEmitter = {
    _events: {},
    dispatch: function (event, data) {
        if (!this._events[event]) return; // no one is listening to this event
        for (var i = 0; i < this._events[event].length; i++)
            this._events[event][i](data);
    },
    subscribe: function (event, callback) {
      if (!this._events[event]) this._events[event] = []; // new event
      this._events[event].push(callback);
    }
}

otherObject.subscribe('namechanged', function(data) { alert(data.name); });
this.dispatch('namechanged', { name: 'John' });

It’s a very simple EventEmitter but it does it job.

If you want to try the Publish/Subscribe system, you can use PubSubJS

Events in React

To use these events manager in React, you have to look at those 2 components functions : componentWillMount and componentWillUnmount.

You want to subscribe only if you component is mounted, thus you have to subscribe in componentWillMount.
Same idea if you component is unmounted, you have to unsubscribe from events, you don’t want to process them anymore, you’re gone. Thus you have to unsubscribe in componentWillUnmount.

The EventEmitter pattern is not very useful when you have to deal with components because we don’t have a reference to them. They are rendered and destroy automatically by React.
The pub/sub pattern seems adequate because you don’t need references.

Here is example where multiple products are displayed, when you click on one of them, it dispatches a message with its name to the topic “products”.
Another component (a brother in the hierarchy here) subscribes to this topic and update its text when it got a message.

// ProductList is just a container
var ProductList = React.createClass({
  render: function() {
    return  
} }); // ProductSelection consumes messages from the topic 'products' // and displays the current selected product var ProductSelection = React.createClass({ getInitialState: function() { return { selection: 'none' }; }, componentWillMount: function() { // when React renders me, I subscribe to the topic 'products' // .subscribe returns a unique token necessary to unsubscribe this.pubsub_token = pubsub.subscribe('products', function(topic, product) { // update my selection when there is a message this.setState({ selection: product }); }.bind(this)); }, componentWillUnmount: function() { // React removed me from the DOM, I have to unsubscribe from the pubsub using my token pubsub.unsubscribe(this.pubsub_token); }, render: function() { return You have selected the product : {this.state.selection}; } }); // A Product is just a
which publish a message to the topic 'products' // when you click on it var Product = React.createClass({ onclick: function() { // when a product is clicked on, we publish a message on the topic 'products' and we pass the product name pubsub.publish('products', this.props.name); }, render: function() { return
{this.props.name}
; } }); React.render(, document.body);

Here is the result :

Then clicking on Product 2 :

ES6: yield and js-csp

A new way to pass messages is to use ES6 with the generators (yield). Take a look here if you’re not afraid : https://github.com/ubolonton/js-csp.

Basically, you have a queue and anyone having a reference to it can put objects inside.
Anyone listening will be ‘stuck’ until a message arrives. When that happens, it will ‘unstuck’ and instantly get the object and continues its execution. The project js-csp is still under development, but still, it’s another solution to this problem. More on that later ;-).

Conclusion

There is not a better solution. It depends on your needs, on the application size, on the numbers of components you have. For small application, you can use props and callback, that will be enough. Later, you can pass to pub/sub to avoid to pollute your components.

Here, we are not talking about data, just components. To handle data request, data changed etc. take a look at the Flux architecture with the stores, Facebook Relay with GraphQL, or Redux, those are very handy.

Published by

ctheu

Hey. I love computer sciences almost since I'm born. I work with frontend and backend technologies; I can't make my mind ! Let's share some tips about how some of them work, shall we ?

  • Thanks for sharing~~

  • In the example “Using the same callback” you should add the state `checked: false` to MyContainer, Right know its value is undefined when you are passing it to the children through `props`, `false` would be cleaner.

    • You’re absolutely right, I’ll update the post. Thanks !

  • Pingback: 萌の宇 – React生命周期、API和深入用法()

  • Ben

    Very good one! thanks for all those clear explanations

  • SocialChooozy

    Thank you for sharing, great article

  • rv_nath

    Nice article. Sums up event communication.

  • Great article – just what I was looking for!

  • NiceCue

    Eureka~
    Thanks for great article.

    사랑해요 !!

  • DongDongDong

    That context stuff I don’t like. It reminds me of inherited scope in angular, and creates obscurity when sharing state between components. I can see why it’s not documented :)

  • Gotzila Za

    Great article.

  • ashwintastic

    what if parent-child relation is build at router .. how to pass props there ??