data binding in react + signalR - signalr

I have a strategy question.
I want to change data in my website using signalR and display changed data using react. My question would be: How to perform data binding between signalR and react?
My first clue is the following:
signalR:
chat.client.addMessage = function (name, message) {
chatHistory.push({ Author: name, Text: message }); //here I change global variable chatHistory
};
react:
var CommentList = React.createClass({some class here});
var CommentBox = React.createClass({
componentRefresh: function () {
this.setState({ data: chatHistory });
},
getInitialState: function () {
return { data: chatHistory };
},
componentDidMount: function () {
this.componentRefresh();
setInterval(this.componentRefresh, this.props.interval);
},
render: function () {
return (
React.DOM.div(null,
CommentList({ data: this.state.data })
)
);
}
});
React.renderComponent(
CommentBox({ interval: 2000 }),
document.getElementById('content')
);
in react commentBox component I feed global chatHistory and ask for a new value every 2 seconds.
Is there more elegant way of doing it?
and how to avoid redrawing of CommentBox if chatHistory variable wasn't changed?

Your approach of maintaining state in CommentBox is fine. As your component base grows, it might become complicated to maintain self-updating components though. I recommend investigating the Flux architecture the React team designed and their Todo MVC Flux example in particular.
You could implement shouldComponentUpdate to prevent React from re-rendering the CommentBox if you know state hasn't changed. Also, you should keep a reference to the interval so you can clear it when the CommentBox is unmounted otherwise it will go on polling after the component is removed.
var CommentBox = React.createClass({
...
componentDidMount: function() {
this.componentRefresh();
this._interval = setInterval(this.componentRefresh, this.props.interval);
},
componentWillUnmount: function() {
clearInterval(this._interval);
this._interval = null;
},
shouldComponentUpdate: function(nextProps, nextState) {
// Do a deep comparison of `chatHistory`. For example, use
// Underscore's `isEqual` function.
return !_.isEqual(this.state.chatHistory, nextState.chatHistory);
},
...
});

Related

Share processed state across components

I have three components. My state has a property named state.selected.
Currently in my mapStateToProps I am doing this in all three components:
function mapStateToProps(state, ownProps) {
return { selected:state.selected }
}
In each presentational component I then do the same processing called getSelectedDisplays. This function does some processing based on what is selected.
var PresentaionalComponent_1 = React.createClass({
render: function() {
var displays = getSelectedDisplays();
// custom processing on `displays` for coponent 1
}
})
var PresentaionalComponent_2 = React.createClass({
render: function() {
var displays = getSelectedDisplays();
// custom processing on `displays` for coponent 2
}
})
var PresentaionalComponent_3 = React.createClass({
render: function() {
var displays = getSelectedDisplays();
// custom processing on `displays` for coponent 3
}
})
No control over parent component
I was hoping to avoid wrapping the three components in an extra div as my only need was to pass to them the result of getSelectedDisplays. I was hoping to avoid this:
React.createElement(OverContainer)
and OverContainer would be the only one receiving state.selected and it would then do getSelectedDisplays then it will render the three components with it as a prop:
var OverPresentaional = React.createClass({
render: function() {
var { selected } = this.props;
var display = getSelectedDisplays(selected);
return React.createElement('div', {},
React.createElement(PresentaionalComponent_1, { display });
React.createElement(PresentaionalComponent_2, { display });
React.createElement(PresentaionalComponent_3, { display });
);
}
}});
Is this possible without wrapping them in a parent div?
You can create a selector, that will encapsulate getting the data from the state, and computing derived properties:
export const getSelectedDisplays = (state) => {
const selected = state.selected;
const selectedDisplays = // whatever logic you need to get selectedDisplays from selected
return {
selectedDisplays;
};
};
Now for each component:
import { getSelectedDisplays } from 'selectorFile';
function mapStateToProps(state, ownProps) {
return getSelectedDisplays(state);
}
var PresentaionalComponent_1 = React.createClass({
render: function() {
var displays = this.props.selectedDisplays;
// custom processing on `displays` for coponent 1
}
})
etc...
The only problem is, that getting the data, and the logic will be performed 3 times, instead of ones. To solve that, you can create a memoized selector, that will cache and return the same result, if the supplied params (state in this case) haven't changed. Reselect is a library the creates memoized selectors for you.

Lazy loading references from normalized Redux store

Yo! I'm using Redux and Normalizr. The API I'm working with sends down objects that look like this:
{
name: 'Foo',
type: 'ABCD-EFGH-IJKL-MNOP'
}
or like this
{
name: 'Foo2',
children: [
'ABCD-EFGH-IJKL-MNOP',
'QRST-UVWX-YZAB-CDEF'
]
}
I want to be able to asynchronously fetch those related entities (type and children) when the above objects are accessed from the state (in mapStateToProps). Unfortunately, this does not seem to mesh with the Redux way as mapStateToProps is not the right place to call actions. Is there an obvious solution to this case that I'm overlooking (other than pre-fetching all of my data)?
Not sure that I have correctly understood your use-case, but if you want to fetch data, one simple common way is to trigger it from a React component:
var Component = React.createClass({
componentDidMount: function() {
if (!this.props.myObject) {
dispatch(actions.loadObject(this.props.myObjectId));
}
},
render: function() {
const heading = this.props.myObject ?
'My object name is ' + this.props.myObject.name
: 'No object loaded';
return (
<div>
{heading}
</div>
);
},
});
Given the "myObjectId" prop, the component triggers the "myObject" fetching after mounting.
Another common way would be to fetch the data, if it's not already here, from a Redux async action creator (see Redux's doc for more details about this pattern):
// sync action creator:
const FETCH_OBJECT_SUCCESS = 'FETCH_OBJECT_SUCCESS';
function fetchObjectSuccess(objectId, myObject) {
return {
type: FETCH_OBJECT_SUCCESS,
objectId,
myObject,
};
}
// async action creator:
function fetchObject(objectId) {
return (dispatch, getState) => {
const currentAppState = getState();
if (!currentAppState.allObjects[objectId]) {
// fetches the object if not already present in app state:
return fetch('some_url_.../' + objectId)
.then(myObject => (
dispatch(fetchObjectSuccess(objectId, myObject))
));
} else {
return Promise.resolve(); // nothing to wait for
}
};
}

RactiveJS - when is it safe to call find(...)

When is it safe to call the find(...) method on a ractive instance and be guaranteed that the template has been rendered and the DOM elements are available?
Background:
I'm new to RactiveJS, building my first application with it. I've been instantiating Ractive instances and then calling methods like find() on those instances to access elements rendered from my templates. Something like this:
var ractive = new Ractive({ el: ..., template: ..., data: ..., etc });
var element = ractive.find('.some-template-element');
This has been working fine so far, but I'm wondering whether I might have a race condition here because of the fact that ractive seems to render templates asynchronously. Is it safe to write code like the above or do I need to instead move everything into callbacks like this?
ractive.on('complete', function() {
var element = ractive.find('.some-template-element');
});
Too Much Information:
In practice, of course, what I'm doing is more complicated than this simple pseudo-code. I'm creating 'widgets' that use Ractive as an internal implementation detail. Right now, I create those widgets and then start calling methods on them. But if the Ractive methods aren't ready to call right away, I'll need to restructure my widgets to expose callbacks/promises that get called once I know my ractives have been created and are ready for use.
I haven't been able to find details in the RactiveJS documentation that explain when it's safe to call the various functions on Ractive, but I'm hoping I've just missed something.
Assuming you've provided an el option, rendering happens synchronously with the new Ractive instantiation.
While you can subscribe via ractive.on( 'event', ... ), it is often handy and cognitively easier to use the onevent options:
var log = [];
var r = new Ractive({
el: document.body,
template: '#template',
data: {
log: log
},
components: {
'child-component': Ractive.extend({
template: '<span>child</span>',
oninit: function() {
log.push('child component init');
},
onrender: function() {
var span = this.find('span');
log.push('child component render find span:' + !!span);
},
oncomplete: function() {
log.push('child component complete');
}
})
},
oninit: function() {
log.push('parent view init');
},
onrender: function() {
var div = this.find('div'),
span = this.find('span');
log.push('parent component render, find div: ' + !!div + ' find span: ' + !!span);
},
oncomplete: function() {
log.push('parent component complete');
}
});
// these won't fire becasue they already happened!
r.on('init', function() {
log.push('view on("init"...)');
});
r.on('render', function() {
log.push('view on("render"...)');
});
// this will fire because complete is always async.
r.on('complete', function() {
log.push('view on("complete"...)');
});
<script src='//cdn.jsdelivr.net/ractive/0.7.3/ractive-legacy.min.js'></script>
<script src='//cdn.jsdelivr.net/ractive.transitions-fade/0.2.1/ractive-transitions-fade.min.js'></script>
<script id='template' type='text/ractive'>
<div intro='fade'>main view</div>
<child-component/>{{#log}}
<li>{{.}} {{/}}
</script>

Why is data set with Meteor Iron Router not available in the template rendered callback?

This is a bit puzzling to me. I set data in the router (which I'm using very simply intentionally at this stage of my project), as follows :
Router.route('/groups/:_id',function() {
this.render('groupPage', {
data : function() {
return Groups.findOne({_id : this.params._id});
}
}, { sort : {time: -1} } );
});
The data you would expect, is now available in the template helpers, but if I have a look at 'this' in the rendered function its null
Template.groupPage.rendered = function() {
console.log(this);
};
I'd love to understand why (presuming its an expected result), or If its something I'm doing / not doing that causes this?
From my experience, this isn't uncommon. Below is how I handle it in my routes.
From what I understand, the template gets rendered client-side while the client is subscribing, so the null is actually what data is available.
Once the client recieves data from the subscription (server), it is added to the collection which causes the template to re-render.
Below is the pattern I use for routes. Notice the if(!this.ready()) return;
which handles the no data situation.
Router.route('landing', {
path: '/b/:b/:brandId/:template',
onAfterAction: function() {
if (this.title) document.title = this.title;
},
data: function() {
if(!this.ready()) return;
var brand = Brands.findOne(this.params.brandId);
if (!brand) return false;
this.title = brand.title;
return brand;
},
waitOn: function() {
return [
Meteor.subscribe('landingPageByBrandId', this.params.brandId),
Meteor.subscribe('myProfile'), // For verification
];
},
});
Issue
I was experiencing this myself today. I believe that there is a race condition between the Template.rendered callback and the iron router data function. I have since raised a question as an IronRouter issue on github to deal with the core issue.
In the meantime, workarounds:
Option 1: Wrap your code in a window.setTimeout()
Template.groupPage.rendered = function() {
var data_context = this.data;
window.setTimeout(function() {
console.log(data_context);
}, 100);
};
Option 2: Wrap your code in a this.autorun()
Template.groupPage.rendered = function() {
var data_context = this.data;
this.autorun(function() {
console.log(data_context);
});
};
Note: in this option, the function will run every time that the template's data context changes! The autorun will be destroyed along with the template though, unlike Tracker.autorun calls.

Can twitter flightjs do component within component

In Facebook react.js, you can compose component within component, or maybe mix and match.
I'm wondering if twitter flight can do the same thing. if so, can anyone gives me an example?
this is what I have so far:
define(function (require) {
var defineComponent = require('flight/lib/component'),
infoInput = require('component/info_input');
return defineComponent(inputSection, infoInput);
function inputSection () {
this.after('initialize', function() {
infoInput.doSomehting();
});
};
});
and my info_input.js is defined below:
define(function (require) {
var defineComponent = require('flight/lib/component');
return defineComponent(infoInput);
function infoInput() {
this.after('initialize', function() {
});
this.doSomething = function() {
alert('I will do something');
};
};
});
This is what mixins are for.
Flight Components are enriched mixins.
From doc/component_api.md
It comes with a set of basic functionality such as event handling and Component registration. Each Component definition mixes in a set of custom properties which describe its behavior.
Read more about Components.
So the answer to your question is Yes.
I guess that what you are doing is legit, although I've never done it before.
I'd rather move the shared logic to a Mixin or attach the two components to the same element and let them talk via events:
component/input_section.js
this.after('initialize', function () {
this.trigger('uiSomethingRequired');
});
component/info_input.js
this.after('initialize', function () {
this.on('uiSomethingRequired', this.doSomething);
});
Solution mentioned by G.G above works!
We may go a step ahead to trigger events on restricted scope instead of document:
component/input_section.js
this.after('initialize', function () {
this.$node.closest(this.attr.parentClass).trigger('uiSomethingRequired');
});
component/info_input.js
this.after('initialize', function () {
this.on(this.$node.closest(this.attr.parentClass), 'uiSomethingRequired', this.doSomething);
});

Resources