Polling not working in React JS mixin - javascript

So I created the following mixin:
var Polling = {
startPolling: function() {
var self = this;
setTimeout(function() {
self.poll();
if (!self.isMounted()) {
return;
}
self._timer = setInterval(self.poll(), 15000);
}, 1000);
},
poll: function() {
if (!this.isMounted()) {
return;
}
var self = this;
console.log('hello');
$.get(this.props.source, function(result) {
if (self.isMounted()) {
self.setState({
error: false,
error_message: '',
users: result
});
}
}).fail(function(response) {
self.setState({
error: true,
error_message: response.statusText
});
});
}
}
Note the console.log('hello'); in the poll function. I should see this every 15 seconds according to this logic.
Now lets look at a react component:
//= require ../../mixins/common/polling.js
//= require ../../mixins/common/state_handler.js
//= require ../../components/recent_signups/user_list.js
var RecentSignups = React.createClass({
mixins: [Polling, StateHandler],
getInitialState: function() {
return {
users: null,
error_message: '',
error: false
}
},
componentDidMount: function() {
this.startPolling();
},
componentWillUnmount: function() {
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state.users !== nextState.users ||
this.state.error !== nextState.error ||
this.state.error_message !== nextState.error_message) {
return true;
}
return false;
},
renderContents: function() {
if (this.state.users === null) {
return;
}
return (
<div>
<ul>
<UserList users={this.state.users} />
</ul>
</div>
);
},
render: function() {
return (
<div>
{this.loading()}
{this.errorMessage()}
{this.renderContents()}
</div>
)
}
});
RecentSignupsElement = document.getElementById("recent-signups");
if (RecentSignupsElement !== null) {
ReactDOM.render(
<RecentSignups source={ "http://" + location.hostname + "/api/v1/recent-signups/" } />,
RecentSignupsElement
);
}
Here we see in the componetDidMount function I am calling this.startPolling When the page loads, what I see after 1 second is:
hello
hello
A) its (poll fucntion) some how being called twice oO.
B) its (poll function) never being called again.
The reason I separated polling out is so that I can use it in other components on the same page and not duplicate code.
Very simply question(s):
Why and how do I fix this? I need it to poll ever 15 seconds and I should only see hello once when poll is called the first time.

On this line you call self.poll() and the result would be the timer:
self._timer = setInterval(self.poll(), 15000);
Instead pass the function:
self._timer = setInterval(self.poll, 15000);

As another option, in the spirit of "you're code's not working? just use someone else's instead!", react-async-poll is a handy component wrapper that you can use for polling.

Related

clearInterval if pagination page is bigger than 1

i'm working on small project using Vue.js i have created a pagination system to display my database users in a table, i have a small issue, i would like to know how can i stop the setinterval if my getResult function page variable is bigger than 1.
this is my code :
data(){
return {
editMode : true,
customer_id : null,
laravelData : {},
formFields : {}
}
},
methods:{
getResults(page = 1){
axios.get('Thirdparty/loadCustomers/' + page).then(response => {
this.laravelData = response.data;
});
}
},
created(){
self = this;
setInterval(function(){
self.getResults();
}, 5000);
}
First and foremost, always capture identifiers from setInterval and setTimeout.
By capturing your interval ID you can later remove it from within your callback when the page value is larger than its default (1).
EDIT: The OP would like to be able to reset the interval when page resets.
created() {
this.resetInterval();
},
methods: {
resetInterval() {
this.currentInterval && clearInterval(this.currentInterval);
this.currentInterval = setInterval(() => this.getResults(), 5000);
},
getResults(page = 1) {
if (page == 1 && !this.currentInterval) {
this.resetInterval();
} else {
clearInterval(this.currentInterval);
}
axios.get('Thirdparty/loadCustomers/' + page).then(response => {
this.laravelData = response.data;
});
}
}
data(){
return {
editMode : true,
customer_id : null,
laravelData : {},
formFields : {},
currentInterval : null
}
},
methods:{
getResults(page = 1){
clearInterval(this.currentInterval);
axios.get('Thirdparty/loadCustomers/' + page).then(response => {
this.laravelData = response.data;
});
},
created(){
self = this;
self.currentInterval = setInterval(function(){
self.getResults();
}, 5000);
}

React JS on page load data not getting loaded, however on click its working

Below is my code:
var CommonHeader = require('./header/CommonHeader.jsx');
var ListOptions = require('./header/ListOptions.jsx');
var SortableTable = require('../shared/SortableTable.jsx');
var ColumnDefinition = require('../shared/SortableTable/ColumnDefinition.jsx');
var DashboardApiActions = require('../../actions-api/DashboardApiActions');
var DashboardStore = require('../../stores/DashboardStore');
function constructList(data) {
var clickFunction = function(dashboardId, e) {
e.preventDefault();
DashboardApiActions.getDetail(dashboardId);
};
return data.map(function(row) {
return {
name : <a href="#" onClick={clickFunction.bind(this, row.id)}>{row.name}</a>,
createdBy : row.createdBy,
shared: "Share to everyone",
popularity: 20
};
});
}
function getState() {
return {
selectedTab: 'dashboard',
pageMetaData : DashboardStore.getPageMetaData(),
hasNextPage : DashboardStore.hasNextPage()
};
}
var List = React.createClass({
getInitialState: function() {
return getState();
},
handleDashboard: function() {
this.setState({
selectedTab: 'dashboard'
});
},
handleFav: function() {
this.setState({
selectedTab: 'fav'
});
},
handlePopular: function() {
this.setState({
selectedTab: 'popular'
});
},
wait: function(ms) {
alert('hi');
var start = new Date().getTime();
var end = start;
while(end < start + ms) {
end = new Date().getTime();
}
},
getDetails() {
var nextPageListener = this.state.hasNextPage ? this.handleNextPage : null;
if(this.state.selectedTab === 'dashboard') {
this.wait(1000);
var details = DashboardStore.getList();
console.log(details);
return (
<SortableTable data={constructList(details)} nextPageListener={nextPageListener} >
<ColumnDefinition dataField="name">Name</ColumnDefinition>
<ColumnDefinition dataField="createdBy">Owner</ColumnDefinition>
<ColumnDefinition dataField="shared">Shared With</ColumnDefinition>
<ColumnDefinition dataField="popularity">Popularity</ColumnDefinition>
</SortableTable>
);
} else if(this.state.selectedTab === 'fav') {
return(
<div className="col-md-12">
<span>Nothing to show</span>
</div>
);
} else if(this.state.selectedTab === 'popular') {
return(
<div className="col-md-12">
<span>Nothing to show</span>
</div>
);
}
},
_onChange : function() {
this.setState(getState());
},
componentDidMount : function() {
DashboardStore.addChangeListener(this._onChange);
},
componentWillUnmount : function() {
DashboardStore.removeChangeListener(this._onChange);
},
handleNextPage : function () {
var currPage = this.state.pageMetaData.pageNumber ? this.state.pageMetaData.pageNumber : 0;
DashboardApiActions.getDashboards(currPage + 1);
},
render: function(){
return(
<div id="dashboard">
<CommonHeader title={"Dashboard"} options={<ListOptions />}
handlePopular={this.handlePopular}
handleDashboard={this.handleDashboard}
handleFav={this.handleFav}/>
{this.getDetails()}
</div>
);
}
});
module.exports = List;
I have 3 tabs. On click of each I need to show some table data. On load My dashboard is selected. The issue is on load table is empty but if I click on some other tab and then again click on My dashboard tab then data is coming.
After debugging thoroughly I understood the problem is time issue, after 1000ms data is coming here -
var details = DashboardStore.getList();
so I called wait() to wait for 1000ms. Now one surprising thing is happening if I add one alert at wait() method then data is coming once I click on ok of alert box. If I remove the alert then on load data not coming anymore.
I checked API is hitting on load and response also coming.
so whats the issue. Please help me. I am stuck for a long time. :-(
It looks like the issue might be that you are using componentDidMount, there is some delay between this function being called and getInitialState so I suspect that you have a race condition between those 2.
Try using componentWillMount instead of componentDidMount.
Like so:
componentWillMount : function() {
DashboardStore.addChangeListener(this._onChange);
},
componentWillUnmount : function() {
DashboardStore.removeChangeListener(this._onChange);
},

Set state after observe()

What's the best way to set state based on the data received from observe()?
It seems setting state via componentWillMount() won't work as observe() runs after this and the data isn't available to set state.
I'm using the observe function as advised when using Parse
E.g.:
var DragApp = React.createClass({
getInitialState: function () {
return {
activeCollection : ''
};
},
observe: function() {
return {
collections: (collectionsQuery.equalTo("createdBy", currentUser))
};
},
_setactiveCollection: function(collection) {
this.setState({
activeCollection : collection
});
},
componentWillMount: function () {
var collection = this.data.collections[0];
this._setActiveCollection(collection);
},
)}
I went the wrong way about this.
I shouldn't be storing this.data into state. I can pass it into components via render.
To get round this.data not being ready before rendering, I make use of the ParseReact function pendingQueries() inside render. E.g.
if (this.pendingQueries().length > 0) {
content = 'loading...'
} else {
content = 'hello world I am' + this.data.name
}
Try:
var DragApp = React.createClass({
observe: function() {
var collections = collectionsQuery.equalTo("createdBy", currentUser);
return {
collections: collections,
activeCollection: collections[0]
};
},
render: function () {
// do something with this.data.collections and/or this.data.activeCollection
},
)}

React does not rerender after setState used in promise

Every time the props are changed, the component will call onTermChange and get the details for this component with a promise that returns an array of objects.
The problem is that when setState is called, nothing happens and the component is not re-rendered with fresh details.
module.exports = React.createClass({
displayName: 'TaxonomySelect',
getInitialState: function () {
return {
children: undefined
};
},
componentDidMount: function () {
this.onTermChange(this.props.term);
},
componentWillReceiveProps: function (nextProps) {
this.props.term = this.props.term || null;
if (this.props.term) {
this.onTermChange(nextProps.term.id);
} else {
this.onTermChange(nextProps.term);
}
},
onTermChange: function (term) {
this.setState({children: undefined});
TaxonomyStore.getTerm(this.props.term)
.bind(this)
.then(function (term) {
TaxonomyStore.merge(9999,{
description: 'No specific topic',
id: 9999
});
if (term.path && term.path.length === 3) {
term.children.unshift(TaxonomyStore.get(9999));
}
console.log(term.id, term.children);
this.setState({children: term.children});
this.forceUpdate();
this.render();
});
},
onSelect: function (id) {
if (this.props.onChange) {
this.props.onChange(TaxonomyStore.get(id));
}
},
render: function () {
if (!Array.isArray(this.state.children) || this.state.children.length < 1) {
return null;
};
var options = this.state.children.map(function (term) {
return {
value: term.id.toString(),
label: term.description
};
});
var value = this.props.value && this.props.value.toString();
return (
<div>
<Select name={this.props.name} value={value} options={options} onChange={this.onSelect} />
</div>
);
}
});
When you call this.setState({children: term.children}); this equals the function it was defined in, not the react component.
Probably an exception occurs, but your promise does not call .error to trap and log it.
You shouldn't need to call this.forceUpdate() if you are calling this.setState. Also, you should never call a component's render method.
It's hard to tell why it's not rerendering but I would a few debugger statements to see whether render is getting called. I'd guess that always calling this.onTermChange in componentWillReceiveProps may be a potential issue.
I came across the same problem, inspired by #z5h, I use a local viraible to refer to this outside of Promise, and it works!
In your case:
onTermChange: function (term) {
this.setState({children: undefined});
let _this = this; // change
TaxonomyStore.getTerm(this.props.term)
.bind(this)
.then(function (term) {
TaxonomyStore.merge(9999,{
description: 'No specific topic',
id: 9999
});
if (term.path && term.path.length === 3) {
term.children.unshift(TaxonomyStore.get(9999));
}
console.log(term.id, term.children);
_this.setState({children: term.children}); //change
});
}
More about this in js: How does the “this” keyword work?

Asynchronous workflow testing in Jasmine 2.0

I have an AngularJS app where I need to test a workflow and guarantee that the correct values are set after an event is broadcasted.
In 1.3 I would do this:
it('should have the correct match workflow', function() {
// matchMaking event
runs(function() {
scope.$broadcast('matchMaking', gameId);
});
waitsFor(function() {
return (scope.match && scope.match.game);
}, 'A game should be defined', 3000);
runs(function() {
expect(scope.match.game).toBeDefined();
});
// matchCreate event
runs(function() {
scope.$broadcast('matchCreate', gameId, {}, {});
});
waitsFor(function() {
return scope.match.status === 'CREATED';
}, 'Match status should be \'CREATED\'', 3000);
runs(function() {
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
});
// matchPrepare event
runs(function() {
scope.$broadcast('matchPrepare');
});
waitsFor(function() {
return scope.match.status === 'PREPARED';
}, 'Match status should be \'PREPARED\'', 3000);
runs(function() {
expect(scope.match.id).toBeDefined();
});
// ... continues
});
With Jasmine 2.0, it seems that the only solution to test a workflow is to chain setTimeout functions inside each other (all expectations must be inside the same spec in order to use the same scope):
beforeEach(inject(function($rootScope, $compile) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
scope = $rootScope;
element = angular.element('<pg-match-making></pg-match-making>');
$compile(element)($rootScope);
$rootScope.$digest();
}));
it('should have the correct match workflow', function(done) {
var timeoutTick = 100;
scope.$broadcast('matchMaking', gameId);
setTimeout(function(){
expect(scope.match.game).toBeDefined();
scope.$broadcast('matchCreate', gameId, {}, {});
setTimeout(function(){
expect(scope.match.status).toEqual('CREATED');
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
scope.$broadcast('matchPrepare');
setTimeout(function(){
expect(scope.match.status).toEqual('PREPARED');
expect(scope.match.id).toBeDefined();
// ... call done() on the last setTimeout()
}, timeoutTick);
}, timeoutTick);
}, 6000);
});
I ended up with a pile of 7 setTimeout which make the source code a lot harder to read and the test terribly slow to run.
Isn't there a better way to test a workflow with Jasmine 2.0?
I have a solution for your problem. I have build a small simple async test framework that works well with Jasmine 2.x, but it uses the jQuery Deferred object to schedule continuations.
function asyncWait(delay) {
return new $.Deferred(function () {
var _self = this;
setTimeout(function () {
_self.resolve();
}, delay || 0);
}).promise();
}
var Async = function(init) {
var d = new $.Deferred(init);
this.promise = d.promise();
d.resolve();
};
Async.prototype.continueWith = function (continuation, delay) {
var _self = this;
_self.promise.then(function () {
_self.promise = asyncWait(delay).then(continuation);
});
return _self;
};
Async.prototype.waitsFor = function (condition, timeout, pollInterval) {
pollInterval = pollInterval || 10;
timeout = timeout || 5000;
var _self = this,
wait_d = new $.Deferred(),
t = 0,
ln = function () {
if (condition()) {
wait_d.resolve();
return;
}
if (t >= timeout) {
wait_d.reject();
throw "timeout was reached during waitsFor";
}
t += pollInterval;
setTimeout(ln, pollInterval);
};
_self.promise.then(ln);
_self.promise = wait_d.promise();
return _self;
};
To use this code, wire up a Jasmine test and use a new instance of the Async class,
it("some async test workflow I want to run", function (done) {
new Async(function () {
//wire up the first async call here
var timeoutTick = 100;
scope.$broadcast('matchMaking', gameId);
}).continueWith(function () {
expect(scope.match.game).toBeDefined();
scope.$broadcast('matchCreate', gameId, {}, {})
}, 6000).continueWith(function () {
//more stuff here
}).waitsFor(function () {
// a latch function with timeout - maybe wait for DOM update or something
return $(".my-statefull-element").val() === "updated";
}, 1000).continueWith(done); //finish by waiting for done to be called
});
This code is not a 100% fool proof, but it works for me. Let me know if you have any issues with it.
With a little bit of extra javascript, you can make the jasmine behave similarly to what you had with 1.3.1, and you don't need to pull in any additional libraries. You just need to implement the polling function that you are missing. Here's a simplified example:
var value1 = false;
var value2 = false;
var value3 = false;
var test1 = function _test1() {
setTimeout( function() { value1 = true; }, 1000 );
}
var test2 = function _test2() {
setTimeout( function() { value2 = true; }, 5000 );
}
var test3 = function _test3() {
setTimeout( function() { value3 = true; }, 300000 );
}
var asyncCheckFn = function( done, waitFor, verify ) {
if ( waitFor() ) {
verify();
done();
} else {
console.log( 'checking...' );
setTimeout( function() { asyncCheckFn(done, waitFor, verify) }, 500);
}
};
describe('async test suite', function() {
it( 'works with short test', function( done ) {
test1();
asyncCheckFn( done, function() {
return value1;
}, function() {
expect( value1 ).toBe( true );
});
}, 3000 );
it( 'longer delay', function( done ) {
test2();
asyncCheckFn( done, function() {
return value2;
}, function() {
expect( value2 ).toBe( true );
});
}, 10000 );
it( 'fails', function( done ) {
test3();
asyncCheckFn( done, function() {
return value3;
}, function() {
expect( value3 ).toBe( true );
});
}, 3000 );
});
The asyncTestFn() performs the same task that the waitsFor() function used to do -- tests a condition until it is true. The overall timeout for the test is controlled by the last parameter passed to the it() function. Here's your example rewritten as a linear test instead of nested setTimeouts:
describe('should have the correct match workflow', function() {
var timerTick = 100;
// matchMaking event
it('defines the game', function(done) {
scope.$broadcast('matchMaking', gameId);
asyncCheckFn(done, function() {
return scope.match && scope.match.game;
}, function() {
expect(scope.match.game).toBeDefined();
});
}, 6000);
it('creates the match', function(done) {
scope.$broadcast('matchCreate', gameId, {}, {});
asyncCheckFn(done, function() {
return scope.match.status === 'CREATED';
}, function() {
expect(scope.match.id).toBeDefined();
expect(scope.match.player).toBeDefined();
expect(scope.match.opponent).toBeDefined();
});
}, timerTick);
it('prepares the match', function(done) {
scope.$broadcast('matchPrepare');
asyncCheckFn(done, function() {
return scope.match.status === 'PREPARED';
}, function() {
expect(scope.match.id).toBeDefined();
});
}, timerTick);
// ... continues
});
Hope this helps.
(I know this is a little old, but I came across the question when trying to solve a similar problem -- how to nest sequential, dependent tests (answer, you can't... ))
(samples tested with Jasmine 2.2)

Categories