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

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

Related

React: axios.get and promises

I wrote the following code to use axios inside getInitialState:
var Player = createReactClass({
readDataFromServer: function() {
return axios.get("**** url address ****")
.then(res => {
return {name: res[name]}
});
},
render: function() {
return this.readDataFromServer().then((res) => {
return (<input value={res.name} type="text"/>)
}
}
}
I'd expect that I'll get an input with text inside. 'render' function returns a only when the promise is resolved, and then it returns
But, still, I just get an error saying:
Player.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
By the way, if I change readDataFromServer function to:
readDataFromServer: function() {
return {name: 'John'}
}
I get an input box with the text 'John' inside.
You would probably have to use react-redux for these async axios calls. When you initiate the app, your getInitialState function doesn't have a return value, so when it first renders it to the DOM, it has an empty value. If you are to console.log the returned value, you will probably see it in the console, however, since the value was null when you first render it, it does not display the returned value. Look into react-redux, where it manages your datastore, once you get the returned value from the axios call, update the redux value, and render the redux value in your
render: function() {
return (<input value={this.redux.name} type="text"/>)
}
call instead, and it will update the DOM automatically once it receives the response form the server.
Feel free to look at the boilerplate for React and Express application as an example: https://github.com/rjzheng/REWBoilerplate.git. Look inside the /app folder and see how reducers are set up.

How to properly test react component methods

I've been developing in React for a while for my work, but recently I was requested to get some applications to ~100% test coverage using Istanbul. I've wrote over 160 tests for this application alone in the past few days, but I haven't been able to cover certain parts of my code. Im having the most trouble covering AJAX calls, setTimeout callbacks, and component methods that require another component to operate properly.
I've read several SO questions to no avail, and I believe that is because I'm approaching this incorrectly. I am using Enzyme, Chai assertions, Mocha, Istanbul coverage, sinon for spies, and was considering nock since I cant get sinon fakeServer working.
Here is the component method in question:
_getCategoriesFromServer() {
const _this = this;
sdk.getJSON(_this.props.sdkPath, {
itemsperpage: 10000
}).done(function(data) {
_this.setState({
isLoaded: true,
categories: _this.state.categories.concat(data)
});
});
}
Here is the test for that component:
it('should call _getCategoriesFromServer', () => {
sinon.spy(CategoryTree.prototype, '_getCategoriesFromServer');
wrapper = mount(<CategoryTree {...props} />);
expect(CategoryTree.prototype._getCategoriesFromServer.calledOnce).to.be.true;
});
The sdk is just a module that constructs a jQuery API call using getJSON.
My test is covering the function call, but its not covering the .done callback seen here:
So my question is, how can I properly test the .done?
If anyone has an article, tutorial, video, anything that explains how to properly test component methods, I would really appreciate it!
Second question is, how can I go about testing a method that gets passed down as a prop to a child component? With the testing coverage requirement I have to have that method tested, but its only purpose is to get passed down to a child component to be used as an onClick. Which is fine, but that onClick is dependent on another AJAX call returning data IN the child component.
My initial impulse was to just use enzymes .find to locate that onClick and simulate a click event, but the element with that onClick isn't there because the AJAX call didn't bring back data in the testing environment.
If you've read this far, I salute you. And if you can help, I thank you!
You could use rewire(https://github.com/jhnns/rewire) to test your component like this:
// let's said your component is ListBox.js
var rewire = require("rewire");
var myComponent = rewire("../components/ListBox.js");
const onDone = sinon.spy()
const sdkMock = {
getJSON (uri, data) {
return this.call('get', uri, data);
},
call: function (method, uri, data) {
return { done: function(){ onDone() } }
}
};
myComponent.__set__("sdk", sdkMock);
and finally you will test if the done function get called like this:
expect(onDone.calledOnce)to.be.true
With this should work as expected. If you need more options you could see all the options of rewire in GitHub.
BABEL
If you are using babel as transpiler you need to use babel-plugin-rewire(https://github.com/speedskater/babel-plugin-rewire) you could use it like this:
sdk.js
function call(method, uri, data) {
return fetch(method, uri, data);
}
export function getJSON(uri, data) {
return this.call('get', uri, data);
}
yourTest.js
import { getJSON, __RewireAPI__ as sdkMockAPI } from 'sdk.js';
describe('api call mocking', function() {
it('should use the mocked api function', function(done) {
const onDone = sinon.spy()
sdkMockAPI.__Rewire__('call', function() {
return { done: function(){ onDone() } }
});
getJSON('../dummy.json',{ data: 'dummy data'}).done()
expect(onDone.calledOnce)to.be.true
sdkMockAPI.__ResetDependency__('call')
})
})

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

Disconnect Reflux listenTo method

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

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