global update event for Vue.js - javascript

In Vue.js, is there a way to register an event if any component updates its data?
My usecase: I am modeling a RPG character via a set of Javascript classes. The TCharacter class has several attributes that can be modified: name, level, HP, magic. While "name" is a simple string, "HP" and "magic" is a custom class TResource which has its own consumption and refill rules.
Instance of the TCharacter class is a source of truth, and I created some Vue components that are views of it.
I created a character component and a resource component in Vue, vaguely like this:
<div class=template id=character>
<input v-model="ch.name">
<resource :attr="ch.magic"></resource>
<resource :attr="ch.hp"></resource>
</div>
<div class="template" id="resource">
you have {{ attr.available }} points
<button #click="attr.consume">X</button>
</div>
<div id="main">
<character :ch="lancelot"></character>
</div>
and the javascript:
class TCharacter {
constructor() {
this.name = "Lancelot"
this.hp = new Resource(20)
this.magic = new Resource(10)
}
}
class TResource {
constructor(limit) {
this.available = limit
this.limit = limit
}
consume() {
if (this.available > 0) this.available--;
}
}
let lancelot = new TCharacter()
Vue.component('character', {
template: '#character',
props: ['ch'],
})
Vue.component('resource', {
template: '#resource',
props: ['attr'],
})
new Vue({
el: "#main",
data() { return { lancelot } }
})
(I'm not sure the code works exactly as written, but hopefully the intent is clear. Something very similar to this is already working for me.)
Now, I'd like to save the character object to localstorage every time the user makes a modification: changes its name, clicks on a button that consumes a point of magic, etc.
So for instance, I want to be notified that the value of ch.name changed because the user typed something into the input box. Or that a magic point was lost because the user clicked a button for that.
I could detect changes to the character component by installing an updated() handler, which notifies me whenever a DOM is modified (viz). However, this won't trigger when the child component resource is modified. I'd need to add a separate updated() handler to all other components. This gets tedious very fast.
I'm imagining something like a global updated() handler that would fire any time any component has registered a change. Or better, a way to specify that update should fire on component's children changes as well.
edit: I have reworded parts of the question to clarify what I'm trying to accomplish.
Some of you already suggested Vuex. But, from what I understood, Vuex enforces being the single source of truth -- I already have a single source of truth. How is Vuex different / better?

You're going to need a serialized version of lancelot to write out. You can do that with a computed. Then you can watch the computed to see when anything changes.
Alternatively, you could watch each individual trait, and write it out as it changes.
class TCharacter {
constructor() {
this.name = "Lancelot"
this.hp = new TResource(20)
this.magic = new TResource(10)
}
}
class TResource {
constructor(limit) {
this.available = limit
this.limit = limit
}
consume() {
if (this.available > 0) this.available--;
}
}
let lancelot = new TCharacter()
Vue.component('character', {
template: '#character',
props: ['ch'],
})
Vue.component('resource', {
template: '#resource',
props: ['attr'],
})
const vm = new Vue({
el: "#main",
data() {
return {
lancelot
}
},
computed: {
serializedLancelot() {
return JSON.stringify(this.lancelot);
}
},
watch: {
serializedLancelot(newValue) {
console.log("Save update:", newValue);
}
}
});
setTimeout(() => {
vm.lancelot.hp.consume();
}, 500);
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="main">
</div>

Am not sure I understand the use case in entirety, but if my assumption is right, you need to update components based on an object's update (updates to properties of an object), for that you could use Vuex . Although am not sure if you are restricted to use an additional library
Here as an example, you could add a state value named character which is an object, something along the lines of
const state = {
character = {};
}
And now you can mutate this using vuex mutations.
commit('set_character', your_new_value)
Now since you said you need to update all or some components based on any mutation to character, use vuex plugins to listen to any mutation to that object, and update the state of the components.
store.subscribe(mutation => {
if (mutation.type === 'set_character') {
// do whatever you want here
}
})
All of the above is just an outline based on what you mentioned, but this is just a starter, you may or may not want to add character into the store's state but simply the properties such as magic or hp.

Related

How to add class to Vue component via $refs

I need to add class name to some Vue components using their ref names. The ref names are defined in a config file. I would like to do it dynamically, to avoid adding class manually on each Vue component.
I have tried to find each component using $refs and if found, add the class name to the element's class list. The class is added, but it is removed as soon as user interaction begins (e.g. the component is clicked, receives new value etc.)
Here is some sample code I've tried:
beforeCreate() {
let requiredFields = config.requiredFields
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
You can use this:
this.$refs[field].$el.classList.value = this.$refs[field].$el.classList.value + 'my-class'
the only thing that you need to make sure of is that your config.requiredFields must include the ref name as a string and nothing more or less ... you can achieve that with :
//for each ref you have
for (let ref in this.$refs) {
config.requiredFields.push(ref)
}
// so config.requiredFields will look like this : ['one','two]
here is an example of a working sample :
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('one', {
template: '<p>component number one</p>'
})
Vue.component('two', {
template: '<p>component number two</p>'
})
new Vue({
el: "#app",
beforeCreate() {
let requiredFields = ['one','two'] // config.requiredFields should be like this
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
})
.my-class {
color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<one ref="one" ></one>
<two ref="two" ></two>
</div>
I know this question was posted ages ago, but I was playing around with something similar and came across a much easier way to add a class to $refs.
When we reference this.$refs['some-ref'].$el.classList it becomes a DOMTokenList which has a bunch of methods and properties you can access.
In this instance, to add a class it is as simple as
this.$refs['some-ref'].$el.classList.add('some-class')
You've to make sure classList.value is an array. By default its a string.
methods: {
onClick(ref) {
const activeClass = 'active-submenu'
if (!this.$refs[ref].classList.length) {
this.$refs[ref].classList.value = [activeClass]
} else {
this.$refs[ref].classList.value = ''
}
},
},
this post helped me tremendously. I needed to target an element within a v-for loop and I ended up writing a little method for it (i'm using Quasar/Vue).
hopefully this will save someone else some time.
addStyleToRef: function(referEl, indexp, classToAdd) {
//will add a class to a $ref element (even within a v-for loop)
//supply $ref name (referEl - txt), index within list (indexp - int) & css class name (classToAdd txt)
if ( this.$refs[referEl][indexp].$el.classList.value.includes(classToAdd) ){
console.log('class already added')
} else {
this.$refs[referEl][indexp].$el.classList.value = this.$refs[referEl][indexp].$el.classList.value + ' ' + classToAdd
}
}
let tag = this.$refs[ref-key][0];
$(tag).addClass('d-none');
Simply get the tag with ref let tag = this.$refs[ref-key][0]; then put this tag into jquery object $(tag).addClass('d-none'); class will be added to required tag.

Whats the right way to manipulate a model instance in React?

So I have a React component that accepts an instance of a function constructor (a Car).
The component's job is to display information about the Car and manipulate it based on the Car's public interface (methods and properties).
In the example below, a child component should add an accident on button click.
Question: What is the right way for the child to manipulate properties of the Car instance? The root parent's state stores reference to the instance of the Car, and the children are able to manipulate the Car's properties (like .accidents), but see the various onChange examples for why I'm struggling to find the right React way to do this.
I'd like to avoid a heavy handed solution like Flux to store this state.
Any suggestions would be appreciated!
function Car(name, color) {
this.name = name;
this.color = color;
this.accidents = [];
}
const myCar = new Car('Ferrari', 'Red');
myCar.accidents.push('accident #1');
class Accident extends React.Component {
handleButton1 = () => {
const newAccident = 'accident type1 # ' + Math.floor(Math.random()*100);
this.props.onChange1(newAccident);
}
handleButton2 = () => {
const newAccident = 'accident type2 # ' + Math.floor(Math.random()*100);
this.props.onChange2(newAccident);
}
handleButton3 = () => {
const newAccident = 'accident type3 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
this.props.onChange3();
}
handleButton4 = () => {
const newAccident = 'accident type4 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
// This circumvents React's state management, so the parent doesnt
// rerender when its state changes.
}
render() {
return (
<div>
<button onClick={this.handleButton1}>
Add accident (onChange1)
</button>
<button onClick={this.handleButton2}>
Add accident (onChange2)
</button>
<button onClick={this.handleButton3}>
Add accident (onChange3)
</button>
<button onClick={this.handleButton4}>
Add accident (option 4)
</button>
<ul>
{this.props.accidents.map((a, i) => <li key={i}>{a}</li>)}
</ul>
</div>
)
}
}
class DisplayCard extends React.Component {
state = {
editingCar: this.props.car
}
// Push the new accident into state and set it with the same reference.
onChange1 = (newAccident) => {
this.state.editingCar.accidents.push(newAccident);
// Is this semantically different than calling this.forceUpdate?
this.setState({
editingCar: this.state.editingCar,
});
}
// Clone the existing state we want to update and explicitly set that new state
onChange2 = (newAccident) => {
const newAccidentList = _.cloneDeep(this.state.editingCar.accidents);
newAccidentList.push(newAccident);
// Setting our new accident list like this converts editingCar to a POJO
// editingCar.name is lost because a deep merge does not happen.
this.setState({
editingCar: {
accidents: newAccidentList
},
});
}
// Just force update - this.state.editingCar was manipulated by <Accident />.
onChange3 = () => {
this.forceUpdate();
}
render() {
return (
<div>
<div>Car Name: {this.state.editingCar.name}</div>
<Accident
accidents={this.state.editingCar.accidents}
onChange1={this.onChange1}
onChange2={this.onChange2}
onChange3={this.onChange3}
/>
</div>
);
}
}
ReactDOM.render(
<DisplayCard car={ myCar } />,
document.getElementById('container')
);
Also on JSFiddle if you want to play around: https://jsfiddle.net/jamis0n003/fbkn5xdy/4/
EDIT: The React JS docs suggest integrating with "other libraries", such as Backbone models, using forceUpdate:
https://reactjs.org/docs/integrating-with-other-libraries.html#using-backbone-models-in-react-components
When state is stored in a parent component and a child component wants to manipulate that state, the parent should pass a callback function to the child's props. Then the child calls the callback to notify the parent to modify its own state. The child should never modify props since the change can have unintended consequences due to the way objects are referenced in JavaScript.
If you want to get really fancy, you can use Redux which stores "global" state in the top-most parent component. All child components issue (or dispatch) actions which notify the top-level parent to update its state which is then passed down again to all children components through their props.
What is the right way for the child to manipulate properties of the Car instance?
In general, rely on setState() to update state, which will reliably redraw the view, or if you mutate the data use forceRedraw() to ensure the view is redrawn with the latest data -- but using setState() is much preferred. In either case a child must notify a parent of a change using a callback like you have, but instead of having the child Accident actually change the data, make it a "dumb" component which notifies the parent of an intended change and the parent actually makes the change.
I'd like to avoid a heavy handed solution like Flux to store this state.
You may want to look into MobX, which is popular alternative to Flux/Redux that is a bit easier to get into because it allows you to mutate objects very much in the way you are already doing.

Knockout: get reference to component A to call one of A's function from component B?

Used Yeoman's Knockout generator (c.a. early 2015) which includes in require.js and router.js. Just using KO's loader.
Am attempting to call a function (ko.observable or not) in component "a" from component "b".
All the fluff below attempts to do merely:
// In componentB:
ComponentA.sayFoo();
Read KO docs on components and loaders, hacked for hours, etc. I don't want the overhead of say postal.js - and also could not get subscriptions (KO pub/sub) to work - I'm guessing for the same reason: the view models set up this way have no references to each other (?) - so the subscribers in one module don't see the publishers in another (right?) (… a bit over my head here %-)
1) Is this because the modules don't see each other… that this generated code does not place the KO stuff in a global namespace?
2) Trying to reach from one module to the other, seems to hinge on the getting the ref via the callback parms, using the function below, or is that incorrect? :
ko.components.get (name, callback) ;
startup.js using require looks like this:
define(['jquery', 'knockout', './router', 'bootstrap', 'knockout-projections'], function($, ko, router) {
// Components can be packaged as AMD modules, such as the following:
ko.components.register('component-a', { require: 'components/a/component-a' });
ko.components.register('component-b', { require: 'components/b/component-b' });
// [Scaffolded component registrations will be inserted here. To retain this feature, don't remove this comment.]
// [Scaffold component's N/A (I think?)]
// Start the application
ko.applyBindings({ route: router.currentRoute });
});
The (component) module A is straight forward, like this:
define(['knockout', 'text!./component-a'], function(ko, templateMarkup) {
function ComponentA (params) { console.log ('CompA'); } ;
ComponentA.prototype.sayFoo = function () { console.log ('FOO!'); } ;
ComponentA.prototype.dispose = function(){};
return { viewModel: ComponentA, template: templateMarkup };
});
Similarly, module B is:
define(['knockout', 'text!./component-b'], function(ko, templateMarkup) {
function ComponentB (params) { console.log ('Compb'); } ;
ComponentB.prototype.doFoo = function () {
//// B Needs to fire ComponentA.foo() … SEE CODE ATTEMPT BELOW
};
ComponentB.prototype.dispose = function(){};
return { viewModel: ComponentB, template: templateMarkup };
});
So this is where I'm stuck:
ComponentB.prototype.doFoo = function () {
ko.components.get ('component-a', ( function (parms) {
console.log ('parms.viewModel : ' + parms.viewModel );
// parms.viewModel is (unexpectedly) undefined ! So how to get the ref?
console.log ('parms.template : ' + parms.template );
// does have expected html objects, eg. [object HTMLwhatever], [object HTML...]
})) ;
This should be easy, or I'm dumbly leaving out something obvious!?
Maybe the modules need to be defined / set up differently?
Any suggestions would assist! Thx
This is just not how you'd normally communicate between knockout components.
Your options are:
1) Use https://github.com/rniemeyer/knockout-postbox. This is probably the best option as it integrates nicely with knockout. It is well documented and if you have troubles setting it up, you can always ask for help here.
2) Use any other global javascript EventBus (f.i. postal.js) and emit/subscribe to events in your components.
3) Have your root ViewModel pass common observables to each component as parameters - that way each component could modify/subscribe to the same observable.
4) (Probably what you want, although the worst scaling solution) If you give ids to the different components you could use ko.dataFor(document.getElementById("id")) to directly access the properties and methods of your components.
EDIT: In response to the comment:
I haven't
been able to determine what / where the root view model is:
ko.applyBindings({ route: router.currentRoute }) is the clue, but
router.js is convoluted. Suggestions on how to determine that?
Exactly - in your case the { route: router.currentRoute } object IS your root ViewModel. It currently only has one property called route, but you could definitely extend that.
For instance:
var rootViewModel = {
route: router.currentRoute,
mySharedObservable: ko.observable('hi!')
}
ko.applyBindings(rootViewModel);
Then you can pass that observable to multiple components as a parameter like this:
<div id="component-a" data-bind="component: { name: 'component-a', params: {router: $root.router, mySharedObservable: $root.mySharedObservable} }"></div>
<div id="component-b" data-bind="component: { name: 'component-b', params: {router: $root.router, mySharedObservable: $root.mySharedObservable} }"></div>
And finally you can use the new observable from within the component like this:
function ComponentB (params) {
this.mySharedObservable = params && params.mySharedObservable;
console.log(this.mySharedObservable());// This should log 'hi!'
};
You can now subscribe to the observable, change it and so on. It will be shared between components, so changing it one component will trigger the subscriptions in all components.
My standard approach would be to control the communication through the parent VM.
The parent VM can create a subscribable[1], and pass it to both componentA and componentB as a parameter; then ComponentA can subscribe to the subscribable, and ComponentB can trigger the subscribable.
//ComponentA
function ComponentA (params) {
var shouldSayFoo = params.shouldSayFoo;
this.shouldSayFooSubscription = shouldSayFoo.subscribe(function () {
this.sayFoo();
});
} ;
ComponentA.prototype.sayFoo = function () { console.log ('FOO!'); } ;
ComponentA.prototype.dispose = function () { this.shouldSayFooSubscription.dispose(); };
//In ComponentB
function ComponentB (params) {
this._triggerFoo = params.triggerFoo; //Same subscribable as shouldSayFoo in ComponentA
}
ComponentB.prototype.doFoo = function () {
this._triggerFoo.notifySubscribers(); //notifySubscribers triggers any .subscription callbacks
}
If ComponentA and ComponentB are siblings and you don't do this sort of stuff all the time, this works as a decently simple solution. If the components are "distant relatives" or if you find yourself doing this a lot, then I'd suggest some sort of pub-sub. And an advantage of this approach can be used by a lot of individual "A-B" pairs without interfering with each other, which is harder with a pub-sub system.
[1]: A ko.subscribable is an object with a subset of the observable functionality (ko.observable inherits from ko.subscribable): it doesn't let you read or write values, but lets you do .subscribe and .notifySubscribers. (They're created with new for some reason) You could use an observable, too, it's just a slight confusion of intent to create an observable if you don't intend it to hold a value.

How do I properly handle change detection on a component that relies on a ViewChild and data from an observable in Angular 2?

So presently, I have a component that fits into a larger dashboard for rendering a graph of a node's immediate parent and child relationships. This component is supposed to refresh its graph every time the node_id input is changed externally.
I've included a simplified version of my code.
#Component({
selector: 'relations',
template: `
<div [class]="'panel panel-' + (_loading ? 'default' : 'primary')">
<div class="panel-heading">Child relations</div>
<div class="panel-body">
<div class="loading" *ngIf="_loading" style="text-align: center">
<img src="./loading.gif" height="100px" width="100px" />
</div>
<div class="graph_container" [style.display]="_loading ? 'none': 'block'" #my_graph></div>
</div>
</div>
`
})
export class GraphComponent implements OnChanges {
#Input('node_id') node_id;
#ViewChild('my_graph') graphDiv;
private _loading: boolean = true;
private _current_node: Node;
private _parent: Node;
private _children: Node[];
constructor(
private _nodeService: NodeService
) {}
ngOnChanges(changes){
this.getRelations();
}
getRelations() {
this._loading = true;
Observable.combineLatest(
this._nodeService.getEvent(this.node_id),
this._nodeService.getChildren(this.node_id),
this._nodeService.getParent(this.node_id)
).subscribe(v => {
this._current_node = v[0];
this._children = v[1];
this._parent = v[2];
this._loading = false
this.renderGraph();
});
}
renderGraph() {
...
}
}
Now the issue I'm having is a race condition; the renderGraph() method relies on the #ViewChild('my_graph') graphDiv variable to know where it should drop the canvas element for rendering the graph. Because of this, when the observable resolves, it may try to call renderGraph() before the #ViewChild component has initialized.
I've tried playing with the lifecycle hooks by doing things such as:
ngAfterViewInit(){
if (!this._loading){
this.renderGraph();
}
}
That only helps if the observable finishes before the view is loaded, and causes no graph to be rendered should the view finish rendering first.
So my question is, how can I properly achieve what I want? That is to say, re-rendering the graph following the observable resolving in response to a change to node_id.
I'm very new at Angular 2 (and front end in general), and my intuition tells me I'm not using the observable in a way it's intended to be used, but I've had difficulty in finding any examples similar to what I want.
Any help/guidance/advice would be greatly appreciated.
Thanks!
I would use BehaviorSubject which is just a special type of Observable. Snippet from the docs:
It stores the latest value emitted to its consumers, and whenever a new Observer subscribes, it will immediately receive the "current value" from the BehaviorSubject.
The reason for preferring BehaviorSubject is because it always emits the last node_id value no matter when the subscription actually happens. In case it was set before viewInit. Also, because it will always have the latest value, we don't need to have node_id property on GraphComponent. We just need a setter for it that will emit the passed value to subscribers and automatically keep it saved on the subject, so every new subscriber will get the current value.
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
...
export class GraphComponent implements OnChanges {
#ViewChild('my_graph') graphDiv;
...
private _nodeIdSubject = new BehaviorSubject(-1);
constructor(...) {}
#Input('node_id')
set node_id(id){ // this is the same as ngOnChanges but will only be triggered if node_id changed
this._nodeIdSubject.next(id);
}
ngAfterViewInit(){ // subscribe to node_id changes after view init
this._nodeIdSubject.filter(n=> n > -1).subscribe(nodeId=> this.getRelations(nodeId));
}
getRelations(nodeId) {
...
}
renderGraph() {
...
}
}
This is probably not be the best approach, but I like it because now you have a stream of node_id that you can manipulate freely.

React.js: can components from different hierarchies talk to each other? [duplicate]

I just got started with ReactJS and am a little stuck on a problem that I have.
My application is essentially a list with filters and a button to change the layout.
At the moment I'm using three components: <list />, < Filters /> and <TopBar />, now obviously when I change settings in < Filters /> I want to trigger some method in <list /> to update my view.
How can I make those 3 components interact with each other, or do I need some sort of global data model where I can just make changes to?
The best approach would depend on how you plan to arrange those components. A few example scenarios that come to mind right now:
<Filters /> is a child component of <List />
Both <Filters /> and <List /> are children of a parent component
<Filters /> and <List /> live in separate root components entirely.
There may be other scenarios that I'm not thinking of. If yours doesn't fit within these, then let me know. Here are some very rough examples of how I've been handling the first two scenarios:
Scenario #1
You could pass a handler from <List /> to <Filters />, which could then be called on the onChange event to filter the list with the current value.
JSFiddle for #1 →
/** #jsx React.DOM */
var Filters = React.createClass({
handleFilterChange: function() {
var value = this.refs.filterInput.getDOMNode().value;
this.props.updateFilter(value);
},
render: function() {
return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
}
});
var List = React.createClass({
getInitialState: function() {
return {
listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
nameFilter: ''
};
},
handleFilterUpdate: function(filterValue) {
this.setState({
nameFilter: filterValue
});
},
render: function() {
var displayedItems = this.state.listItems.filter(function(item) {
var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
return (match !== -1);
}.bind(this));
var content;
if (displayedItems.length > 0) {
var items = displayedItems.map(function(item) {
return <li>{item}</li>;
});
content = <ul>{items}</ul>
} else {
content = <p>No items matching this filter</p>;
}
return (
<div>
<Filters updateFilter={this.handleFilterUpdate} />
<h4>Results</h4>
{content}
</div>
);
}
});
React.renderComponent(<List />, document.body);
Scenario #2
Similar to scenario #1, but the parent component will be the one passing down the handler function to <Filters />, and will pass the filtered list to <List />. I like this method better since it decouples the <List /> from the <Filters />.
JSFiddle for #2 →
/** #jsx React.DOM */
var Filters = React.createClass({
handleFilterChange: function() {
var value = this.refs.filterInput.getDOMNode().value;
this.props.updateFilter(value);
},
render: function() {
return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
}
});
var List = React.createClass({
render: function() {
var content;
if (this.props.items.length > 0) {
var items = this.props.items.map(function(item) {
return <li>{item}</li>;
});
content = <ul>{items}</ul>
} else {
content = <p>No items matching this filter</p>;
}
return (
<div className="results">
<h4>Results</h4>
{content}
</div>
);
}
});
var ListContainer = React.createClass({
getInitialState: function() {
return {
listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
nameFilter: ''
};
},
handleFilterUpdate: function(filterValue) {
this.setState({
nameFilter: filterValue
});
},
render: function() {
var displayedItems = this.state.listItems.filter(function(item) {
var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
return (match !== -1);
}.bind(this));
return (
<div>
<Filters updateFilter={this.handleFilterUpdate} />
<List items={displayedItems} />
</div>
);
}
});
React.renderComponent(<ListContainer />, document.body);
Scenario #3
When the components can't communicate between any sort of parent-child relationship, the documentation recommends setting up a global event system.
There are multiple ways to make components communicate. Some can be suited to your usecase. Here is a list of some I've found useful to know.
React
Parent / Child direct communication
const Child = ({fromChildToParentCallback}) => (
<div onClick={() => fromChildToParentCallback(42)}>
Click me
</div>
);
class Parent extends React.Component {
receiveChildValue = (value) => {
console.log("Parent received value from child: " + value); // value is 42
};
render() {
return (
<Child fromChildToParentCallback={this.receiveChildValue}/>
)
}
}
Here the child component will call a callback provided by the parent with a value, and the parent will be able to get the value provided by the children in the parent.
If you build a feature/page of your app, it's better to have a single parent managing the callbacks/state (also called container or smart component), and all childs to be stateless, only reporting things to the parent. This way you can easily "share" the state of the parent to any child that need it.
Context
React Context permits to hold state at the root of your component hierarchy, and be able to inject this state easily into very deeply nested components, without the hassle to have to pass down props to every intermediate components.
Until now, context was an experimental feature, but a new API is available in React 16.3.
const AppContext = React.createContext(null)
class App extends React.Component {
render() {
return (
<AppContext.Provider value={{language: "en",userId: 42}}>
<div>
...
<SomeDeeplyNestedComponent/>
...
</div>
</AppContext.Provider>
)
}
};
const SomeDeeplyNestedComponent = () => (
<AppContext.Consumer>
{({language}) => <div>App language is currently {language}</div>}
</AppContext.Consumer>
);
The consumer is using the render prop / children function pattern
Check this blog post for more details.
Before React 16.3, I'd recommend using react-broadcast which offer quite similar API, and use former context API.
Portals
Use a portal when you'd like to keep 2 components close together to make them communicate with simple functions, like in normal parent / child, but you don't want these 2 components to have a parent/child relationship in the DOM, because of visual / CSS constraints it implies (like z-index, opacity...).
In this case you can use a "portal". There are different react libraries using portals, usually used for modals, popups, tooltips...
Consider the following:
<div className="a">
a content
<Portal target="body">
<div className="b">
b content
</div>
</Portal>
</div>
Could produce the following DOM when rendered inside reactAppContainer:
<body>
<div id="reactAppContainer">
<div className="a">
a content
</div>
</div>
<div className="b">
b content
</div>
</body>
More details here
Slots
You define a slot somewhere, and then you fill the slot from another place of your render tree.
import { Slot, Fill } from 'react-slot-fill';
const Toolbar = (props) =>
<div>
<Slot name="ToolbarContent" />
</div>
export default Toolbar;
export const FillToolbar = ({children}) =>
<Fill name="ToolbarContent">
{children}
</Fill>
This is a bit similar to portals except the filled content will be rendered in a slot you define, while portals generally render a new dom node (often a children of document.body)
Check react-slot-fill library
Event bus
As stated in the React documentation:
For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event.
There are many things you can use to setup an event bus. You can just create an array of listeners, and on event publish, all listeners would receive the event. Or you can use something like EventEmitter or PostalJs
Flux
Flux is basically an event bus, except the event receivers are stores. This is similar to the basic event bus system except the state is managed outside of React
Original Flux implementation looks like an attempt to do Event-sourcing in a hacky way.
Redux is for me the Flux implementation that is the closest from event-sourcing, an benefits many of event-sourcing advantages like the ability to time-travel. It is not strictly linked to React and can also be used with other functional view libraries.
Egghead's Redux video tutorial is really nice and explains how it works internally (it really is simple).
Cursors
Cursors are coming from ClojureScript/Om and widely used in React projects. They permit to manage the state outside of React, and let multiple components have read/write access to the same part of the state, without needing to know anything about the component tree.
Many implementations exists, including ImmutableJS, React-cursors and Omniscient
Edit 2016: it seems that people agree cursors work fine for smaller apps but it does not scale well on complex apps. Om Next does not have cursors anymore (while it's Om that introduced the concept initially)
Elm architecture
The Elm architecture is an architecture proposed to be used by the Elm language. Even if Elm is not ReactJS, the Elm architecture can be done in React as well.
Dan Abramov, the author of Redux, did an implementation of the Elm architecture using React.
Both Redux and Elm are really great and tend to empower event-sourcing concepts on the frontend, both allowing time-travel debugging, undo/redo, replay...
The main difference between Redux and Elm is that Elm tend to be a lot more strict about state management. In Elm you can't have local component state or mount/unmount hooks and all DOM changes must be triggered by global state changes. Elm architecture propose a scalable approach that permits to handle ALL the state inside a single immutable object, while Redux propose an approach that invites you to handle MOST of the state in a single immutable object.
While the conceptual model of Elm is very elegant and the architecture permits to scale well on large apps, it can in practice be difficult or involve more boilerplate to achieve simple tasks like giving focus to an input after mounting it, or integrating with an existing library with an imperative interface (ie JQuery plugin). Related issue.
Also, Elm architecture involves more code boilerplate. It's not that verbose or complicated to write but I think the Elm architecture is more suited to statically typed languages.
FRP
Libraries like RxJS, BaconJS or Kefir can be used to produce FRP streams to handle communication between components.
You can try for example Rx-React
I think using these libs is quite similar to using what the ELM language offers with signals.
CycleJS framework does not use ReactJS but uses vdom. It share a lot of similarities with the Elm architecture (but is more easy to use in real life because it allows vdom hooks) and it uses RxJs extensively instead of functions, and can be a good source of inspiration if you want to use FRP with React. CycleJs Egghead videos are nice to understand how it works.
CSP
CSP (Communicating Sequential Processes) are currently popular (mostly because of Go/goroutines and core.async/ClojureScript) but you can use them also in javascript with JS-CSP.
James Long has done a video explaining how it can be used with React.
Sagas
A saga is a backend concept that comes from the DDD / EventSourcing / CQRS world, also called "process manager".
It is being popularized by the redux-saga project, mostly as a replacement to redux-thunk for handling side-effects (ie API calls etc). Most people currently think it only services for side-effects but it is actually more about decoupling components.
It is more of a compliment to a Flux architecture (or Redux) than a totally new communication system, because the saga emit Flux actions at the end. The idea is that if you have widget1 and widget2, and you want them to be decoupled, you can't fire action targeting widget2 from widget1. So you make widget1 only fire actions that target itself, and the saga is a "background process" that listens for widget1 actions, and may dispatch actions that target widget2. The saga is the coupling point between the 2 widgets but the widgets remain decoupled.
If you are interested take a look at my answer here
Conclusion
If you want to see an example of the same little app using these different styles, check the branches of this repository.
I don't know what is the best option in the long term but I really like how Flux looks like event-sourcing.
If you don't know event-sourcing concepts, take a look at this very pedagogic blog: Turning the database inside out with apache Samza, it is a must-read to understand why Flux is nice (but this could apply to FRP as well)
I think the community agrees that the most promising Flux implementation is Redux, which will progressively allow very productive developer experience thanks to hot reloading. Impressive livecoding ala Bret Victor's Inventing on Principle video is possible!
OK, there are few ways to do it, but I exclusively want focus on using store using Redux which makes your life much easier for these situations rather than give you a quick solution only for this case, using pure React will end up mess up in real big application and communicating between Components becomes harder and harder as the application grows...
So what Redux does for you?
Redux is like local storage in your application which can be used whenever you need data to be used in different places in your application...
Basically, Redux idea comes from flux originally, but with some fundamental changes including the concept of having one source of truth by creating only one store...
Look at the graph below to see some differences between Flux and Redux...
Consider applying Redux in your application from the start if your application needs communication between Components...
Also reading these words from Redux Documentation could be helpful to start with:
As the requirements for JavaScript single-page applications have
become increasingly complicated, our code must manage more state than
ever before. This state can include server responses and cached data,
as well as locally created data that has not yet been persisted to the
server. UI state is also increasing in complexity, as we need to
manage active routes, selected tabs, spinners, pagination controls,
and so on.
Managing this ever-changing state is hard. If a model can update
another model, then a view can update a model, which updates another
model, and this, in turn, might cause another view to update. At some
point, you no longer understand what happens in your app as you have
lost control over the when, why, and how of its state. When a system
is opaque and non-deterministic, it's hard to reproduce bugs or add
new features.
As if this wasn't bad enough, consider the new requirements becoming
common in front-end product development. As developers, we are
expected to handle optimistic updates, server-side rendering, fetching
data before performing route transitions, and so on. We find ourselves
trying to manage a complexity that we have never had to deal with
before, and we inevitably ask the question: is it time to give up? The
answer is no.
This complexity is difficult to handle as we're mixing two concepts
that are very hard for the human mind to reason about: mutation and
asynchronicity. I call them Mentos and Coke. Both can be great in
separation, but together they create a mess. Libraries like React
attempt to solve this problem in the view layer by removing both
asynchrony and direct DOM manipulation. However, managing the state of
your data is left up to you. This is where Redux enters.
Following in the steps of Flux, CQRS, and Event Sourcing, Redux
attempts to make state mutations predictable by imposing certain
restrictions on how and when updates can happen. These restrictions
are reflected in the three principles of Redux.
This is the way I handled this.
Let's say you have a <select> for Month and a <select> for Day.
The number of days depends on the selected month.
Both lists are owned by a third object, the left panel. Both <select> are also children of the leftPanel <div>
It's a game with the callbacks and the handlers in the LeftPanel component.
To test it, just copy the code into two separated files and run the index.html. Then select a month and see how the number of days changes.
dates.js
/** #jsx React.DOM */
var monthsLength = [0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
var MONTHS_ARR = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
var DayNumber = React.createClass({
render: function() {
return (
<option value={this.props.dayNum}>{this.props.dayNum}</option>
);
}
});
var DaysList = React.createClass({
getInitialState: function() {
return {numOfDays: 30};
},
handleMonthUpdate: function(newMonthix) {
this.state.numOfDays = monthsLength[newMonthix];
console.log("Setting days to " + monthsLength[newMonthix] + " month = " + newMonthix);
this.forceUpdate();
},
handleDaySelection: function(evt) {
this.props.dateHandler(evt.target.value);
},
componentDidMount: function() {
this.props.readyCallback(this.handleMonthUpdate)
},
render: function() {
var dayNodes = [];
for (i = 1; i <= this.state.numOfDays; i++) {
dayNodes = dayNodes.concat([<DayNumber dayNum={i} />]);
}
return (
<select id={this.props.id} onChange = {this.handleDaySelection}>
<option value="" disabled defaultValue>Day</option>
{dayNodes}
</select>
);
}
});
var Month = React.createClass({
render: function() {
return (
<option value={this.props.monthIx}>{this.props.month}</option>
);
}
});
var MonthsList = React.createClass({
handleUpdate: function(evt) {
console.log("Local handler:" + this.props.id + " VAL= " + evt.target.value);
this.props.dateHandler(evt.target.value);
return false;
},
render: function() {
var monthIx = 0;
var monthNodes = this.props.data.map(function (month) {
monthIx++;
return (
<Month month={month} monthIx={monthIx} />
);
});
return (
<select id = {this.props.id} onChange = {this.handleUpdate}>
<option value="" disabled defaultValue>Month</option>
{monthNodes}
</select>
);
}
});
var LeftPanel = React.createClass({
dayRefresh: function(newMonth) {
// Nothing - will be replaced
},
daysReady: function(refreshCallback) {
console.log("Regisering days list");
this.dayRefresh = refreshCallback;
},
handleMonthChange: function(monthIx) {
console.log("New month");
this.dayRefresh(monthIx);
},
handleDayChange: function(dayIx) {
console.log("New DAY: " + dayIx);
},
render: function() {
return(
<div id="orderDetails">
<DaysList id="dayPicker" dateHandler={this.handleDayChange} readyCallback = {this.daysReady} />
<MonthsList data={MONTHS_ARR} id="monthPicker" dateHandler={this.handleMonthChange} />
</div>
);
}
});
React.renderComponent(
<LeftPanel />,
document.getElementById('leftPanel')
);
And the HTML for running the left panel component
index.html
<!DOCTYPE html>
<html>
<head>
<title>Dates</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script src="//fb.me/react-0.11.1.js"></script>
<script src="//fb.me/JSXTransformer-0.11.1.js"></script>
</head>
<style>
#dayPicker {
position: relative;
top: 97px;
left: 20px;
width: 60px;
height: 17px;
}
#monthPicker {
position: relative;
top: 97px;
left: 22px;
width: 95px;
height: 17px;
}
select {
font-size: 11px;
}
</style>
<body>
<div id="leftPanel">
</div>
<script type="text/jsx" src="dates.js"></script>
</body>
</html>
I saw that the question is already answered, but if you'd like to learn more details, there are a total of 3 cases of communication between components:
Case 1: Parent to Child communication
Case 2: Child to Parent communication
Case 3: Not-related components (any component to any component) communication
I once was where you are right now, as a beginner you sometimes feel out of place on how the react way to do this. I'm gonna try to tackle the same way I think of it right now.
States are the cornerstone for communication
Usually what it comes down to is the way that you alter the states in this component in your case you point out three components.
<List /> : Which probably will display a list of items depending on a filter
<Filters />: Filter options that will alter your data.
<TopBar />: List of options.
To orchestrate all of this interaction you are going to need a higher component let's call it App, that will pass down actions and data to each one of this components so for instance can look like this
<div>
<List items={this.state.filteredItems}/>
<Filter filter={this.state.filter} setFilter={setFilter}/>
</div>
So when setFilter is called it will affect the filteredItem and re-render both component;. In case this is not entirely clear I made you an example with checkbox that you can check in a single file:
import React, {Component} from 'react';
import {render} from 'react-dom';
const Person = ({person, setForDelete}) => (
<div>
<input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
{person.name}
</div>
);
class PeopleList extends Component {
render() {
return(
<div>
{this.props.people.map((person, i) => {
return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
})}
<div onClick={this.props.deleteRecords}>Delete Selected Records</div>
</div>
);
}
} // end class
class App extends React.Component {
constructor(props) {
super(props)
this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
}
deleteRecords() {
const people = this.state.people.filter(p => !p.checked);
this.setState({people});
}
setForDelete(person) {
const checked = !person.checked;
const people = this.state.people.map((p)=>{
if(p.id === person.id)
return {name:person.name, checked};
return p;
});
this.setState({people});
}
render () {
return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
}
}
render(<App/>, document.getElementById('app'));
Extending answer of #MichaelLaCroix when a scenario is that the components can't communicate between any sort of parent-child relationship, the documentation recommends setting up a global event system.
In the case of <Filters /> and <TopBar /> don't have any of the above relationship, a simple global emitter could be used like this:
componentDidMount - Subscribe to event
componentWillUnmount - Unsubscribe from event
React.js and EventSystem code
EventSystem.js
class EventSystem{
constructor() {
this.queue = {};
this.maxNamespaceSize = 50;
}
publish(/** namespace **/ /** arguments **/) {
if(arguments.length < 1) {
throw "Invalid namespace to publish";
}
var namespace = arguments[0];
var queue = this.queue[namespace];
if (typeof queue === 'undefined' || queue.length < 1) {
console.log('did not find queue for %s', namespace);
return false;
}
var valueArgs = Array.prototype.slice.call(arguments);
valueArgs.shift(); // remove namespace value from value args
queue.forEach(function(callback) {
callback.apply(null, valueArgs);
});
return true;
}
subscribe(/** namespace **/ /** callback **/) {
const namespace = arguments[0];
if(!namespace) throw "Invalid namespace";
const callback = arguments[arguments.length - 1];
if(typeof callback !== 'function') throw "Invalid callback method";
if (typeof this.queue[namespace] === 'undefined') {
this.queue[namespace] = [];
}
const queue = this.queue[namespace];
if(queue.length === this.maxNamespaceSize) {
console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
queue.shift();
}
// Check if this callback already exists for this namespace
for(var i = 0; i < queue.length; i++) {
if(queue[i] === callback) {
throw ("The exact same callback exists on this namespace: " + namespace);
}
}
this.queue[namespace].push(callback);
return [namespace, callback];
}
unsubscribe(/** array or topic, method **/) {
let namespace;
let callback;
if(arguments.length === 1) {
let arg = arguments[0];
if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
namespace = arg[0];
callback = arg[1];
}
else if(arguments.length === 2) {
namespace = arguments[0];
callback = arguments[1];
}
if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
const queue = this.queue[namespace];
if(queue) {
for(var i = 0; i < queue.length; i++) {
if(queue[i] === callback) {
queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
return;
}
}
}
}
setNamespaceSize(size) {
if(!this.isNumber(size)) throw "Queue size must be a number";
this.maxNamespaceSize = size;
return true;
}
isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
}
NotificationComponent.js
class NotificationComponent extends React.Component {
getInitialState() {
return {
// optional. see alternative below
subscriber: null
};
}
errorHandler() {
const topic = arguments[0];
const label = arguments[1];
console.log('Topic %s label %s', topic, label);
}
componentDidMount() {
var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
this.state.subscriber = subscriber;
}
componentWillUnmount() {
EventSystem.unsubscribe('error.http', this.errorHandler);
// alternatively
// EventSystem.unsubscribe(this.state.subscriber);
}
render() {
}
}
There is such possibility even if they are not Parent - Child relationship - and that's Flux. There is pretty good (for me personally) implementation for that called Alt.JS (with Alt-Container).
For example you can have Sidebar that is dependent on what is set in component Details. Component Sidebar is connected with SidebarActions and SidebarStore, while Details is DetailsActions and DetailsStore.
You could use then AltContainer like that
<AltContainer stores={{
SidebarStore: SidebarStore
}}>
<Sidebar/>
</AltContainer>
{this.props.content}
Which would keep stores (well I could use "store" instead of "stores" prop). Now, {this.props.content} CAN BE Details depending on the route. Lets say that /Details redirect us to that view.
Details would have for example a checkbox that would change Sidebar element from X to Y if it would be checked.
Technically there is no relationship between them and it would be hard to do without flux. BUT WITH THAT it is rather easy.
Now let's get to DetailsActions. We will create there
class SiteActions {
constructor() {
this.generateActions(
'setSiteComponentStore'
);
}
setSiteComponent(value) {
this.dispatch({value: value});
}
}
and DetailsStore
class SiteStore {
constructor() {
this.siteComponents = {
Prop: true
};
this.bindListeners({
setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
})
}
setSiteComponent(data) {
this.siteComponents.Prop = data.value;
}
}
And now, this is the place where magic begin.
As You can see there is bindListener to SidebarActions.ComponentStatusChanged which will be used IF setSiteComponent will be used.
now in SidebarActions
componentStatusChanged(value){
this.dispatch({value: value});
}
We have such thing. It will dispatch that object on call. And it will be called if setSiteComponent in store will be used (that you can use in component for example during onChange on Button ot whatever)
Now in SidebarStore we will have
constructor() {
this.structures = [];
this.bindListeners({
componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
})
}
componentStatusChanged(data) {
this.waitFor(DetailsStore);
_.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}
Now here you can see, that it will wait for DetailsStore. What does it mean? more or less it means that this method need to wait for DetailsStoreto update before it can update itself.
tl;dr
One Store is listening on methods in a store, and will trigger an action from component action, which will update its own store.
I hope it can help you somehow.
If you want to explore options of communicating between components and feel like it is getting harder and harder, then you might consider adopting a good design pattern: Flux.
It is simply a collection of rules that defines how you store and mutate application wide state, and use that state to render components.
There are many Flux implementations, and Facebook's official implementation is one of them. Although it is considered the one that contains most boilerplate code, but it is easier to understand since most of the things are explicit.
Some of Other alternatives are flummox fluxxor fluxible and redux.
The following code helps me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls.
It is based on https://reactjs.org/docs/refs-and-the-dom.html
Hope it helps.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
Oddly nobody mentioned mobx. The idea is similar to redux. If I have a piece of data that multiple components are subscribed to it, then I can use this data to drive multiple components.

Categories

Resources