Does react make it unnecessary to clean up view objects? - backbone.js

Different sources suggest that unbinding events in react becomes a no-brainer. Is it true? In Backbone it is not unusual to have an instance function just for cleanup.
So when migrating to react, it is not necessary anymore to have close functions attached to views, in order to prevent memory leakage and other ugly effects?

If you write something like
var Test = React.createClass({
handleClick: function() { ... },
render: function() {
return <div onClick={this.handleClick}>...</div>;
}
});
then React will clean up the event handlers when you unmount Test (whether you call React.unmountComponentAtNode or have the parent not render it).
If you add other event in componentDidMount, you should clean them up in componentWillUnmount to prevent leaks.

Related

Defining attributes in React.js

I was wondering is there any difference between these two ways of defining React component attributes:
var something = React.createClass({
SSEStream: new EventSource("/stream/"),
componentDidMount: function() {
this.SSEStream.addEventListener("message", function(msg) {
// do something
}.bind(this));
});
var something = React.createClass({
componentDidMount: function() {
this.SSEStream = new EventSource("/stream/");
this.SSEStream.addEventListener("message", function(msg) {
// do something
}.bind(this));
}
});
Note the difference of how the React component attribute SSEStream was defined. My guess is that in the second example attribute is being recreated every time component is re-rendered whereas in the first it is created only once and therefore the first way should be preferred.
So the question is, will there be even the slightest difference between the two?
The difference between the two is as follows:
The first instantiates and sets a single EventSource at the time the component is declared, which is shared between each instance of the component.
On the other hand, the second creates a separate EventSource for each instance of the component, when the callback is fired.
Assuming that you want multiple instances of the component to be independent of one another, then I guess that the second option is what you want.
By the way, the componentDidMount callback is typically only run once in the life-cycle of the component, when the component is first mounted, so this has nothing to do with re-renders.

Redux for use in mapping applications

I know that redux is great for handling the global state of an application, and when that state is updated to reflect that in the view. However, is it possible to use it on a react component that shouldn't re-render? I have a map which uses leaflet and rather than re-render and plot the data, I want it to plot the data without re-rendering.
Does redux seem like a good choice? If so, where would I handle api calls as I was told it should not be done in the reducer.
Currently my app consists of a nav bar, a fullscreen map and a search menu which is populated from an api request. The idea is that when a search is selected it populates data onto the map.
Can you share how you're using Leaflet and React without Redux?
Essentially, React components will always re-render if their state changes, so you can either have a React component that updates on every change, or a Redux-connected component that updates on every subscribed state change. Doesn't make a difference either way.
If you want React/Redux to be 'aware' of your leaflet widget, the only way to do that is to have it re-render on change. Bear in mind that a 'render' function doesn't just throw away and rebuild that part of the DOM, so re-rendering on every change won't cause your leaflet component to be destroyed and rebuilt.
You could connect the Redux dispatcher but not the state, so that you can publish changes to your server through redux, but not have the state connected. This doesn't seem like the ideal approach to use though.
Another approach is to have a 'persistedMapCoordinates' property that is only set when the user confirms their selection, but not on every change. That way the re-render only happens when they lock in their change, not on every small adjustment.
For doing the API calls, you'll want to use redux thunk and middleware. There is tons of info about this available online :)
If your component doesn't re-render, then I'd suggest not complicating it with Redux. It sounds like you just need a component that manages its own rendering.
var MyMapComponent = React.createClass({
componentWillMount: function() {
fetch('/some/data')
.then(this.update);
},
update: function(data) {
// calling setState will trigger shouldComponentUpdate
this.setState({ data: data });
},
loadMap: function(container) {
// calling setState will trigger shouldComponentUpdate
this.setState({
map: L.map(container)
});
},
shouldComponentUpdate: function(nextProps, nextState) {
var map = this.state.map;
var data = this.state.data;
// make updates to map here
// prevent react from re-rendering this component
return false;
},
render: function() {
// pass a reference to the dom node out to loadMap
return (
<div ref={this.loadMap}></div>
);
}
});

Learning reactJs Flux and encounter the addEventListener confusion

I am learn reactjs flux pattern from the link below
https://scotch.io/tutorials/getting-to-know-flux-the-react-js-architecture
I get completely confused and lost with this following bit, when will the following code ever been trigger or used? i do not see any where in the app where the this._onChange will be trigger... please help and explain any suggestion is appreciated as i am start to learn.
// Listen for changes
componentDidMount: function() {
ShoeStore.addChangeListener(this._onChange);
},
// Unbind change listener
componentWillUnmount: function() {
ShoesStore.removeChangeListener(this._onChange);
},
in the store, does it means in order to trigger the update need to run ShoesStore.emitChange()?
// Emit Change event
emitChange: function() {
this.emit('change');
},
// Add change listener
addChangeListener: function(callback) {
this.on('change', callback);
},
// Remove change listener
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
In typical Flux app, your store emit change (as EventEmitter) and _onChange is invoked because it has been assigned by addChangeListner, which needs to be removed afterwards otherwise it cause memory leak. componentDidMount and componentWillUnmount is invoked at certain life cycle phases, as method names say, just after the component is mounted, and just before unmounted.
Correct answer: (summary from BinaryMuse)
When you're creating a store you'll usually call emitChange() yourself (it's not usually automatic).

How to remove Cordova specific events outside Angular controller?

Imagine I have a controller which handles, for example, view changes:
function Controller($scope){
var viewModel = this;
viewModel.goBack= function(){
viewModel.visible = visibleLinks.pop(); //get last visible link
viewModel.swipeDirection = 'left';// for view change animation
}
}
But I want to handle it not only for example with HTML buttons inside <body>, but also with Back button on device. So I have to add Event Listener for deviceready event, and also explicit call $scope.$apply() in order to fact, that it is called outside of AngularJS context, like this:
document.addEventListener("deviceready", function(){
document.addEventListener("backbutton", function(){
viewModel.goBack();
$scope.$apply();
}, false);
}, false);
}
But I also want to follow (relatively :) ) new controllerAssyntax, cause this is recommended now e.g. by Todd Motto: Opinionated AngularJS styleguide for teams and it allows to remove $scope from controllers when things like $emit or $on are not used. But I can't do it, case I have to call $apply() cause my context is not Angular context when user clicks on device back button. I thought about creating a Service which can be wrapper facade for cordova and inject $scope to this service but as I read here: Injecting $scope into an angular service function() it is not possible. I saw this: Angular JS & Phonegap back button event and accepted solution also contains $apply() which makes $scope unremovable. Anybody knows a solution to remove Cordova specific events outside Angular controller, in order to remove $scope from controllers when not explicity needed? Thank you in advance.
I don't see a reason why to remove the $scope from the controller. It is fine to follow the best practice and to remove it if not needed, but as you said you still need it for $emit, $on, $watch.. and you can add it $apply() in the list for sure.
What I can suggest here as an alternative solution is to implement a helper function that will handle that. We can place it in a service and use $rootScope service which is injectable.
app.factory('utilService', function ($rootScope) {
return {
justApply: function () {
$rootScope.$apply();
},
createNgAware: function (fnCallback) {
return function () {
fnCallback.apply(this, arguments);
$rootScope.$apply();
};
}
};
});
// use it
app.controller('SampleCtrl', function(utilService) {
var backBtnHandler1 = function () {
viewModel.goBack();
utilService.justApply(); // instead of $scope.$apply();
}
// or
var backBtnHandler2 = utilService.createNgAware(function(){
viewModel.goBack();
});
document.addEventListener("backbutton", backBtnHandler2, false);
});
In my case I was simply forwarding Cordova events with the help of Angular $broadcast firing it on the $rootScope. Basically any application controller would then receive this custom event. Listeners are attached on the configuration phase - in the run block, before any controller gets initialized. Here is an example:
angular
.module('app', [])
.run(function ($rootScope, $document) {
$document.on('backbutton', function (e) {
// block original system back button behavior for the entire application
e.preventDefault();
e.stopPropagation();
// forward the event
$rootScope.$broadcast('SYSTEM_BACKBUTTON', e);
});
})
.controller('AppCtrl', function ($scope) {
$scope.$on('SYSTEM_BACKBUTTON', function () {
// do stuff
viewModel.goBack();
});
});
Obviously in the $scope.$on handler you do not have to call $scope.$apply().
Pros of this solution are:
you'll be able to modify an event or do something else for the entire application before the event will be broadcasted to all the controllers;
when you use $document.on() every time controller is instantiated, the event handler stays in the memory unless you manually unsibscribe from this event; using $scope.$on cares about it automatically;
if the way a system dispatches Cordova event changes, you'll have to change it in one place
Cons:
you'll have to be careful when inheriting controllers which already have an event handler attached on initialization phase, and if you want your own handler in a child.
Where to place the listeners and the forwarder is up to you and it highly depends on your application structure. If your app allows you could even keep all the logic for the backbutton event in the run block and get rid of it in controllers. Another way to organize it is to specify a single global callback attached to $rootScope for example, which can be overriden inside controllers, if they have different behavior for the back button, not to mess with events.
I am not sure about deviceready event though, it fires once in the very beginning. In my case I was first waiting for the deviceready event to fire and then was manually bootstrapping AngularJS application to provide a sequential load of the app and prevent any conflicts:
document.addEventListener('deviceready', function onDeviceReady() {
angular.element(document).ready(function () {
angular.bootstrap(document.body, ['app']);
});
}, false);
From my point of view the logic of the app and how you bootstrap it should be separated from each other. That's why I've moved listener for backbutton to a run block.

Flux pattern to initialize Store

Good Morning All!
I've a react Component (a View) that's dependent on a Store which is in turn dependent on having some state pulled from a round-trip to the server.
What I'm looking to understand is if there's a common pattern to solve for initializing the Store's state.
Right now I'm thinking I'd do something like:
var SomeView = React.createClass({
componentWillMount: function() {
SomeStore.addChangeListener(this._onChange);
// Go and tell this thing we want to initiliaze our
// state ahead of time. My worry here is obviously
// that when state is updated this fires again so I'd
// need to have some knowledge that the store has been
// initialized which seems very (very) kludgey
SomeActions.init();
},
render: function() {
// Here i'd want to see if I had items available for
// rendering. If I didn't I'd drop on a loading dialog
// or if I did I could render the detail.
},
_onChange: function() {
// this.setState...
}
});
var SomeActions = {
init: function() {
AppDispatcher.dispatch({
actionType: SomeConstants.INIT
});
}
};
var SomeStore = assign({}, EventEmitter.prototype, {
init: function() {
$.get('/round/trip', function(data) {
this.emitChange();
}).bind(this);
}
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
}
});
AppDispatcher.register(function(action) {
switch(action.actionType) {
case SomeConstants.INIT:
SomeStore.init()
break;
default:
}
});
I am absolutely positive there must be a better way.
My worry here is obviously that when state is updated this fires again
componentWillMount fires once component injected to DOM, state updates will not fire this method. However, if you remove component from DOM (for example: not rendering it in parent component based on some condition) and render it later, the method will be fired again. So init will be called multiple times on the store.
I believe you should move http request code to Web API module and fire an action to the API from componentWillMount method, the API will then trigger the store and fire change event on the component, which will update the state and re-render. This is how Flux works.
If you need to get data only once and you know your component is going to be removed from/added to DOM multiple times, you should put a call to the api into upper component in the tree (component that represents an entry point to the widget or something).
I recommend to check Component Container or Higher-order Components pattern, which basically defines a thin wrapper component as a data layer over the view component. Thus you can completely separate your views from data layer and it works good.
You may also want to check another approach, coming from ClojureScript's Om, with a single immutable state. This simplifies everything even more and actually the best way I've found for my self to build apps with React. I've create a starter kit for it, there's a good explanation of main concepts in the readme.

Resources