ReactJS - is there a "main" component that doesn't own its children?
I have a bunch of components that cannot have the same parent component, but I would like them to share their state.
For the sake of discussion, let's say the top-level component is named Main
, and all the children are named Child1
, Child2
etc.
Do I have a way to have Child1
and Child2
child elements Main
, only in the sense of the React, so I can pass the property Main
and point to them from Main
the component?
In terms of the DOM, Child1
both Child2
are located in completely different areas of my page, and there are actually about 40 of them.
source to share
Instead of calling setState directly, you can use an event emitter and all instances can apply a payload to their state.
We can use a mixin for abstract behavior.
var makeChangeEventMixin = function(emitter){
return {
componentDidMount: function(){
emitter.addListener('change', this.__makeChangeEventMixin_handler_);
},
componentWillUnmount: function(){
emitter.removeListener('change', this.__makeChangeEventMixin_handler_);
},
__makeChangeEventMixin_handler_: function(payload){
this.setState(payload);
},
emitStateChange: function(payload){
emitter.emit('change', payload);
}
};
};
Then in our code we will use EventEmitter (you can use EventEmitter2 if you are not using browser / webpack)
var EventEmitter = require('events').EventEmitter;
var fooEvents = new EventEmitter();
var Counter = React.createClass(){
mixins: [makeChangeEventMixin(fooEvents)],
getInitialState: function(){ return {count: 0} },
render: function(){return <div onClick={this.handleClick}>{this.state.count}</div>},
handleClick: function(){
this.emitStateChange({count: this.state.count + 1});
}
};
And if you have 100 counters, they will update when you click any of them.
One limitation of this is that the initial state will be wrong. You can work around this by tracking the state in makeChangeEventMixin, concatenating it yourself and using replaceState instead of setState. It will also be more effective. If you do this, the mixin can implement the correct getInitialState.
source to share
One of my components, called ItemRenderer, calls renderComponent at three places in the DOM on mount and every update (and only renders an empty div to its real container):
https://github.com/Khan/perseus/blob/a1811f6b7a/src/item-renderer.jsx#L66-L130
This is usually a little tricky to reason about because components don't go where the parent ItemRenderer points them, but it might work if you need to.
source to share