Disconnect Reflux listenTo method - reactjs

So, I have two stores. First pageStore serves business logic of specific page, and second globalStore logic of Android/iOS global events.
When user enters specific page React.componentDidMount calls
pageEntered: function () {
this.listenTo(globalStore, this.locationUpdated);
},
so from this my pageStore started to listen global storage for GPS updates. But is there any way to disconnect listenTo on React.componentWillUnmount ?

There's an example how to unsubscribe from a store listening (taken from the official examples):
var Status = React.createClass({
getInitialState: function() { },
onStatusChange: function(status) {
this.setState({
currentStatus: status
});
},
componentDidMount: function() {
this.unsubscribe = statusStore.listen(this.onStatusChange);
},
componentWillUnmount: function() {
this.unsubscribe();
},
render: function() {
// render specifics
}
});

Here's one way to think about what's happening in the example above:
var myFunc = function(){
console.log("This gets fired immediately");
var randomNumber = Math.ceil(Math.random()*10);
return function() {
return randomNumber;
}
}
var a = myFunc(); //Console log fires IMMEDIATELY, a is the returned function
a(); //some integer from 1 to 10
Since myFunc is invoked when we assign it to a variable, the console.log fires immediately-- it is like this.unsubscribe = statusStore.listen(this.onStatusChange); which "turns on" the listener immediately once componentDidMount happens.
In the componentDidMount lifecycle method, we are attaching a listener to using .listen. That's invoked. Just for convenience, we are assigning the result of the function to this.unsubscribe.
If you look at lines 60-68 of this gist (https://gist.github.com/spoike/ba561727a3f133b942dc#file-reflux-js-L60-L68) think of .listen returning a function that that removes the event listener.
In componentWillUnmount, we invoke this.unsubscribe which removes the listener. You can think of .listen as returning a function that removes the 'listener' and when the componentWillUnmount lifecycle happens we invoke that function and kill the listener.
Tl;dr: Imagine .listen attaches a listener AND returns a function that turns off the listener-- when you first invoke it the listener on and when you invoke the function that gets returned it turns off the listener

Related

Refresh of scope after remote change to data

In my controller for a mpbile app based on Angular1 is have (for example) the following function:
var getItem = function() {
// Initialize $scope
$scope.url = "(url to get my data)";
$http.get($scope.url).success(function(data) {
$scope.itemDetails = data; // get data from json
});
};
getItem();
and this works just fine.. with one problem.. it doesnt update. Even if I switch pages and come back, if the scope hasnt changed, it doesnt reflect new data in the scope.
So, i built in an $interval refresh to look for changes in the scope, this works fine EXCEPT, when i leave the page to go to another, that interval keeps polling. This is obviously a bad idea in a mobile app where data and battery usage may be an issue.
So.. how can I keep checking the scope for 'live changes' when ON that page only OR what is best practice for the scope to refresh on data changes.
I have read about digests and apply but these still seem to be interval checks which I suspect will keep operation after switching pages.
Or on angular apps with live data, is constantly polling the API the 'thing to do' (admittedly the data the page pulls is only 629 bytes, but i have a few pages to keep live data on, so it will add up)
Thanks
When you create a controller, the function's in it are declared, but not run. and since at the end of the controller you are calling getItem(); it is run once.
Moving to another page, and coming back is not going to refresh it.
The only way to refresh is to call that function again, In your HTML or JS.
For example:
<button ng-click="getItem()">Refresh</button>
Really nice question, I have been wondering the same thing, so I checked a lot of related SO posts and wrote kind of a function that can be used.
Note: I am testing the function with a simple console.log(), please insert your function logic and check.
The concept is
$interval is used to repeatedly run the function($scope.getItem) for a period (in the below example for 1 second), A timeout is also actively running to watch for inactive time, this parameter is defined by timeoutValue (in the example its set to 5 seconds), the document is being watched for multiple events, when any event is triggered, the timeout is reset, if the timeoutValue time is exceeded without any events in the document another function is called where the interval is stopped. then on any event in the document after this, the interval is started back again.
var myModule = angular.module('myapp',[]);
myModule.controller("TextController", function($scope, $interval, $document, $timeout){
//function to call
$scope.getItem = function() {
console.log("function");
};
//main function
//functionName - specify the function that needs to be repeated for the intervalTime
//intervalTime - the value is in milliseconds, the functionName is continuously repeated for this time.
//timeoutValue - the value is in milliseconds, when this value is exceeded the function given in functionName is stopped
monitorTimeout($scope.getItem, 1000 ,5000);
function monitorTimeout(functionName, intervalTime, timeoutValue){
//initialization parameters
timeoutValue = timeoutValue || 5000;
intervalTime = intervalTime || 1000;
// Start a timeout
var TimeOut_Thread = $timeout(function(){ TimerExpired() } , timeoutValue);
var bodyElement = angular.element($document);
/// Keyboard Events
bodyElement.bind('keydown', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('keyup', function (e) { TimeOut_Resetter(e) });
/// Mouse Events
bodyElement.bind('click', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousemove', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('DOMMouseScroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousewheel', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousedown', function (e) { TimeOut_Resetter(e) });
/// Touch Events
bodyElement.bind('touchstart', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('touchmove', function (e) { TimeOut_Resetter(e) });
/// Common Events
bodyElement.bind('scroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('focus', function (e) { TimeOut_Resetter(e) });
function TimerExpired(){
if(theInterval) {
$interval.cancel(theInterval);
theInterval = undefined;
}
}
function TimeOut_Resetter(e){
if(!theInterval){
theInterval = $interval(function(){
functionName();
}.bind(this), intervalTime);
}
/// Stop the pending timeout
$timeout.cancel(TimeOut_Thread);
/// Reset the timeout
TimeOut_Thread = $timeout(function(){ TimerExpired() } , timeoutValue);
}
var theInterval = $interval(function(){
functionName();
}.bind(this), intervalTime);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp">
<div ng-controller="TextController">
</div>
</div>
Depending on the router you are using, you have to tell the controller to reload when the route changed or updated, because the function you pass when declaring a controller is only a factory, and once the controller is constructed it won't run again because the router caches it (unless you tell angularjs to do so, which is rarely a good idea).
So your best bet is to use the router to reload the state when the route changes. You can do this using the router event change and update that is broadcast in the scope.
If you are using angularjs' router (a.k.a., ngRoute):
$scope.$on('$routeChangeUpdate', getItem);
$scope.$on('$routeChangeSuccess', getItem);
If you are using ui.router:
$scope.$on('$stateChangeUpdate', getItem);
$scope.$on('$stateChangeSuccess', getItem);
Note: in ui.router you can add cache: false on the state declaration and it'll prevent the controller and the view to be cached.

How flux can improve this case

Please see demo here.
There are two select, act as filter. Changing main select will change the sub select. When either of them changed, I will send their values to the server, and get the async result.
The problem is when or where do I make the async call. In the demo above, I make async call when I change main select or sub select.
onMainSelectChange: function(value) {
this.setState({
mainSelectValue: value
});
this.onSubSelectChange(this.options[value][0]);
this.getAsyncResult();
},
Because setState is async, and I need to send the latest state to the server, I do this in getAsyncResult
getAsyncResult: function() {
var self = this;
// wait for setState over, so we can get the latest state
setTimeout(function() {
var params = self.state.mainSelectValue + self.state.subSelectValue;
// send params to server, get result
setTimeout(function() {
self.setState({
asyncResult: params + Date.now()
});
}, 300);
}, 20);
},
Using setTimeout here feels hacky to me. I wonder if flux can help improve this case.
Well, the signature of the method setState is actually :
void setState(
function|object nextState,
[function callback]
)
There is an optional callback parameter that will be called when state is set. So you can totally use it to trigger your request to the server. So your piece of code would look like that :
onMainSelectChange: function(value) {
this.setState({
mainSelectValue: value
}, this.getAsyncResult.bind(this));
this.onSubSelectChange(this.options[value][0]);
}
And you can then remove your first setTimeout call

React Js: How to reload the initial data loaded via ajax?

I get my initial data from an outside JSON in
componentDidMount: function() { .... $.get(jsonfile, function(data) { ....
But the state of "jsonfile" changes via an input.
When the state of "jsonfile" prop changes the render method is invoked again, but I will also want to re-run the $.get request.
What would be the proper (react) way to do it?
You should abstract away your data fetching. If you put your fetching of data in a separate helper method you can call that method when needed, and it should do the fetching (and later updating) of the state
React.createClass({
componentDidMount: function () {
this.fetchData();
},
fetchData: function () {
var _this = this;
$.get('....', function (result) {
_this.setState(result);
});
},
handleClick: function () {
this.fetchData();
},
render: function () {
return (<div onClick={this.handleClick}>{data}</div>);
},
});
Please do upload some code from your project if you can.
From what I understand, you are calling an API to produce a JSON response and you have a user input to the same json?
In that case if you want to make supplementary calls to the API, you should place your API call in the correct Lifecycle Methods provided by a react component.
Please see https://facebook.github.io/react/docs/component-specs.html

Test fails then succeeds

Click run a couple of times - these tests alternate between pass and fail.
http://jsfiddle.net/samselikoff/hhk6u/3/
Both tests require companies, but I don't know how to isolate the events. Any ideas?
Answer:
Jeferson is correct. One easy way to solve this, is to use events.once instead of events.on. This way you clean up your events from each test.
You are running synchronous tests while the callbacks of the triggered events are asynchronous.
To fix that you have to implement an "asyncTest" and call the start function when the test assertions are ready to be collected.
Your second test was failing with the message:
Called start() while already started (QUnit.config.semaphore was 0
already)
teste
Exactly because it was a synchronous test, already started and you were calling the start() method again.
And also in your first test, that doesn't specify a callback function, you have to wrap your async call in another function so you can call start() when the simulated AJAX call is ready.
I updated your JSFiddle with working code: http://jsfiddle.net/hhk6u/8/
The new code is:
QUnit.config.autostart = false;
QUnit.config.testTimeOut = 1000;
asyncTest('Some test that needs companies.', function() {
function getCompanies() {
var companies = new Companies();
ok(1);
start();
}
setTimeout(getCompanies, 500);
});
asyncTest('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
Note that in the first test method I created a "getCompanies" function that will be called after an interval (500 milliseconds) that should be enough for the AJAX call to finish.
You have to adjust this time according to your needs, and also ajust "testTimeOut" value so your methods won't run indefinitely.
See QUnit config docs for more details: http://api.qunitjs.com/QUnit.config/
Isn't your initial Fiddle potentially failing because you are not creating your event bus at the start of each test (in a setup() method), so your asynchronous event from the first test could be fired when the 2nd test is running and then cause the 2nd test to handle it twice, calling start() twice.
See my updated Fiddle http://jsfiddle.net/e67Zh/ it creates the event bus each time.
You might want to also set a timeout in your qunit tests for scenarios where the event doesn't fire.
/* Backbone code
*******************/
var Company = Backbone.Model.extend({});
var Companies = Backbone.Collection.extend({
initialize: function() {
var self = this;
events.on("userSet:company", function(company) {
self.selectedCompany = company;
// Simulate an AJAX request
setTimeout(function() {
events.trigger("fetched:departments", [1, 2, 3]);
}, 500);
});
},
selectedCompany: ''
});
/* Tests
*******************/
module("test with new event bus each time", {
setup: function() {
events = _.clone(Backbone.Events);
}
});
test('Some test that needs companies.', function() {
var companies = new Companies();
ok(1);
});
test('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});

The best way to trigger an event when the model.fetch is complete

I am looking for the best way to trigger an event when the fetch is completed.
This code works but I am curious to know if there is a better way to accomplish the following task.
var myApp = new Marionette.Application();
myApp.on("initialize:before", function () {
this.currentUser = new UserModel();
this.currentUser.fetch({
complete: function () {
myApp.vent.trigger('currentUser');
}
});
});
A successful fetch triggers a "change" event:
fetch model.fetch([options])
[...] A "change" event will be triggered if the server's state differs from the current attributes.
So if the fetch does anything, there will be a "change" that you can listen for:
myApp.on("initialize:before", function () {
this.currentUser = new UserModel();
this.currentUser.on('change', function() {
myApp.vent.trigger('currentUser');
});
this.currentUser.fetch();
});
That will also trigger a "currentUser" event if this.currentUser is changed in some other way and that might or might not be what you want. If you only want your function called once then your current success handler is probably the easiest thing to do; you could still use on and unbind the handler when it is called:
myApp.on("initialize:before", function () {
var triggerCurrentUser = _.bind(function() {
myApp.vent.trigger('currentUser');
this.currentUser.off('change', triggerCurrentUser);
}, this);
this.currentUser = new UserModel();
this.currentUser.on('change', triggerCurrentUser);
this.currentUser.fetch();
});
You could also use _.once but that would leave a no-op callback function kicking around and there's no need for that.

Resources