Making ajax call in Reactjs, value coming empty while rendering - reactjs

I am new to React.js and trying to render the result from an ajax call using react but the data is not coming to the render() function. If you know any other best way to solve this issue or make an ajax call in react then please let mention the link for the same. Need to render data in react from an API.Below is the javascript for the same. The value is coming in componentDidMount method and result is having the value. But when I try to access in the render, then its empty. I tried to assign value to a global object array but that is also not working. Any solutions for this.The link mentioned in the post for fetching data is a working link. You can call that link in the browser and check for json field.
var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function(result) {
this.setState({
username = result.description,
lastGistUrl = result.html_url
})
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
React.createElement("div", null, this.state.username, " 's last gist's url is ",
React.createElement("div", null, this.state.lastGistUrl, ""), ".")
}
});
ReactDOM.render(React.createElement(UserGist, { source: "https://api.github.com/users/octocat/gists" }), document.getElementById('container'));
Fiddler link

Your code contains syntax errors. Replace = with :.
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function(result) {
this.setState({
username: result.description,
lastGistUrl: result.html_url
})
}.bind(this));
},
More errors to come:
render missing return statement. Should be return React.createElement
result is an array not an object. You need to somehow handle this. For example pick the first element result = result[0]
setState is method. You should call it not make an assignment setState=({}) should be setState({}) This one was in demo code.
See fixed demo.

Related

ReactJS, I want to retrigger an ajax call in ComponentDidMount

-I have a Component (let's call it StorageComponent) that get a data(JSON) from a restful api (the request is made in componentDidMount)
-StorageComponent then passes the data to a child component and it will do stuff with other components to display the data and interact with user.
-Now there is another hierarchy of components independent of the above.
-This handle some form inputs by users, there is one component for each form input (radio button, checkbox, text, etc). And because every re-render will get rid of any state of the child, I had to use an object literal (call it ModelObject) to store each form input. So whenever a user enter something in a form it will make a call to Modelobject and store it there, the component will also ask for data from ModelObject.
-After The user entered all input he will eventually hit a submit button component in this hierarchy, where it will make a call to ModelObject to do ajax POST to the RestAPI. My problem is here, I would like for ModelComponent to get the data from the RestAPI again, so user will see the updated data. I thought forceUpdate() would work, I thought it would re-trigger rendering and thus componentDidMount in StorageComponent.
So what is the best way to do this. Moreover is there any bad practice mentioned above? Is this enough information?
edit:
the storageComponent hierarchy
var StorageComponent= React.createClass({
getInitialState: function(){
return {
data: []
};
},
componentDidMount: function(){
this.serverRequest = $.get(this.props.source, function(result){
result = JSON.parse(result);
this.setState({
data: result
});
}.bind(this));
},
render: function() {
return (
<div>
<Nav dataList={this.state.data} /> //whole bunch of other child component below this one
</div>
);
}
});
app.storageComponent= React.render(
<HabitModel source = "/api/listing/user"/>,
document.getElementById('myDiv')
);
the ModelObject that I've mentioned:
var formModel = {
newInfo: {
inputBox: "",
frequency: "",
date: "",
days: []
},
addDescription: function(description){
this.newHabitInfo.description = description;
},
addFrequency: function(selection){
this.newHabitInfo.frequency = selection;
},
addDay: function(startDay){
this.newHabitInfo.startDay = startDay;
},
getFrequency: function(){
return this.newHabitInfo.frequency;
},
//this is the function I want the second hierarchy of components to
//use to force the storageComponent to do the re-trigger the ajax
updateHabitListing: function(){
if(this.validate()){
app.habitListing.forceUpdate();
}else{
console.log("form not finish");
}
}

ReactJS Read Json from url real time

I am looking into ReactJS. I've created database and added a table with some simple data.
Next I created a php file that will display the data in json format like this:
[{"ID":"1","Name":"name","Color":"green"}]
Next I've created a reactjs skeleton page and I'm trying to get it to read and display the json data but cannot find a simple example anywhere.
How can I get reactjs to read json from a url?
I created a fiddle which is a simple getting started and will give you an idea how to proceed.
Also, I think you will find this example from React Docs about loading data from ajax helpful. Code from there is as follows:
var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},
componentDidMount: function() {
$.get(this.props.source, function(result) {
var lastGist = result[0];
if (this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}
}.bind(this));
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
React.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);

if(this.isMounted()) not being called?

Can you tell me why when I do this:
var SomeComponent = React.createClass({
getData: function(){
if (this.isMounted()){
var queryInfo = {
userId: sessionStorage.getItem("user_id"),
userRole: sessionStorage.getItem('user_role'),
aptId : this.props.params
}
io = io.connect();
io.emit('allTasks', queryInfo);
io.on('allTasksInfo', function(data){
reqwest({
url: '/apartment/tasks/address',
method: 'get',
xhrFields: {withCredentials: true},
crossOrigin: true
}).then(function(data){
this.setState({
dataSet: arr
})
}.bind(this));
}.bind(this));
}
},
componentDidMount: function(){
this.getData();
},
render: function(){...}
});
The code inside the if is executed, but I get the Uncaught Error: Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
But when I do this:
var SomeComponent = React.createClass({
getData: function(){
var queryInfo = {
userId: sessionStorage.getItem("user_id"),
userRole: sessionStorage.getItem('user_role'),
aptId : location.pathname.split("/")[4]
}
reqwest({
url:'/operation/staff',
method: 'get',
xhrFields: {withCredentials: true},
crossOrigin: true
}).then(function(data){
if(this.isMounted()){
this.setState({
operationStaff: data
})
}
}.bind(this));
}
componentDidMount: function(){
this.getData();
},
render: function(){...}
});
Everything is ok. Shouldn't the first just be executed when the component is mounted? What I am missing?
EDIT: I'm using react-router and express server with socket.io with server rendering (just the components, not the data - this I will fetch client side). After the answers, I can say:
The component is not unmounting
I can now tell that at first render, this warning doesn't appear even on second example:
https://drive.google.com/file/d/0B1rbX9C6kejlbWVKeTZ6WVdGN0E/view?usp=sharing
But if I change the url and get back to this path (and here yes, the component unmounts off course), the Ajax reqwest is being called 2 times
https://drive.google.com/file/d/0B1rbX9C6kejlUjFRYTBtejVLZGs/view?usp=sharing
This has something to do with the sockets implementation.
I will close this issue and open another regarding this. Thank you for the help.
Shouldn't the first just be executed when the component is mounted?
Yes, and it is (what makes you think it is not?).
However, the Ajax callback itself is executed some time in the future and at that moment, the component may already be unmounted.
In the first example, the test is useless since the component is always mounted after componentDidMount was called. In the second example, you are testing whether the component is mounted just before you call setState, which makes more sense.
Here is a simplified example:
var Hello = React.createClass({
getInitialState: function() {
return {name: 'foo'};
},
componentDidMount: function() {
console.log('mounted...');
setTimeout(function() {
// this works fine
console.log('updating state once...');
this.setState({
name: 'bar'
});
}.bind(this), 1000);
setTimeout(function() {
// this will throw
console.log('updating state twice...');
this.setState({
name: 'baz'
});
}.bind(this), 3000);
},
componentWillUnmount: function() {
console.log('unmounting...');
},
render: function() {
return <div>Hello {this.state.name}</div>;
}
});
React.render(
<Hello />,
document.getElementById('container')
);
setTimeout(function() {
React.unmountComponentAtNode(
document.getElementById('container')
);
}, 2000);
If you run it you will notice that the second timeout will generate the same error because it is called after the component was unmounted:
Console output:
mounted...
updating state once...
unmounting...
updating state twice...
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
DEMO: https://jsfiddle.net/pkzfbcr5/
I'm using react-router and express server with socket.io with server rendering (just the components, not the data - this I will fetch client side). After the answers, I can say:
The component is not unmounting
I can now tell that at first render, this warning doesn't appear even on second example: https://drive.google.com/file/d/0B1rbX9C6kejlbWVKeTZ6WVdGN0E/view?usp=sharing
But if I change the url and get back to this path (and here yes, the component unmounts off course), the Ajax reqwest is being called 2 times https://drive.google.com/file/d/0B1rbX9C6kejlUjFRYTBtejVLZGs/view?usp=sharing
This has something to do with the sockets implementation.
I will close this issue and open another regarding this. Thank you for the help.

How to freely use pojo's, having setters, as state in React

Hitting a roadbump, I’m seeking some help. I'm starting to move more of my "state" pojo's out of my React components, due to for example being unsure how how my pojo’s setter methods should be utilized now (one may want setter methods to validate, etc.). I now find myself either defying React docs' warning to NEVER touch this.state directly or moving most code except rendering – including state - outside of the React component into my own js variables/objects (and holding a reference to the rendered object then using forceUpdate() to rerender). What is the recommended way to freely use whatever plain old js data/model objects I want, including with setter methods?
This barebones example, where I’m wanting a form-backing data object, demonstrates this difference I’m facing: http://jsfiddle.net/jL0rf0ed/ vs. http://jsfiddle.net/rzuswg9x/. Also pasted the code for the first below.
At the very least, I have this specific question: following a custom/manual update of this.state, does a this.setState(this.state) line, which would be from within the React component, and a component.forceUpdate() line, which would likely be from outside the React component, work just as fast and correctly as the standard this.setState({someKey: someValue})?
Thanks.
//props: dataObj, handleInputChange
test.ComponentA = React.createClass({
getInitialState: function() {
return {
age: 21,
email: 'a#b.com', //TODO make members private
setEmail: function(val) { //TODO utilize this
this.email = val;
if(val.indexOf('#') == -1) {
//TODO set or report an error
}
}
}
},
handleInputChange: function(e) {
this.state[e.target.name]=e.target.value; //defying the "NEVER touch this.state" warning (but it appears to work fine)!
this.setState(this.state); //and then this strange line
},
render: function() {
return (
<div>
<input type='text' name='age' onChange={this.handleInputChange} value={this.state.age}></input>
<input type='text' name='email' onChange={this.handleInputChange} value={this.state.email}></input>
<div>{JSON.stringify(this.state)}</div>
</div>
);
}
});
React.render(<test.ComponentA />, document.body);
For your code example in your pasted snippet, you can do the following.
handleInputChange: function(e) {
var updates = {};
updates[e.target.name] = e.target.value;
this.setState(updates);
},
In your second example, you should never call forceUpdate or setState from outside the component itself. The correct way would be for the state to be contained in whatever renders your component and pass in the data as props.
Usually this means you have a wrapper component.
var RootComponent = React.createClass({
getInitialState: ...
onInputChange: function() {
this.setState({yourKey: yourValue});
},
render: function() {
return <SubComponent yourKey={this.state.yourKey} onInputChange={this.onInputChange} />;
}
};
In your case, I would recommend creating this wrapper component. Another solution is just to rerender the same component into the same DOM node.
test.handleInputChange = function(e) {
// update test.formPojo1 here
React.render(<test.ComponentA dataObj={test.formPojo1} handleInputChange={...} />);
}
Because it is the same component class and DOM node, React will treat it as an update.
Stores
Facebook uses the concept of a Store in their Flux architecture.
Stores are a very targeted POJO. And I find that it is pretty simple to use the Store metaphors without the using the entirety of Flux.
Sample Store
This is a Store that I pulled out of one of our production React apps:
ChatMessageStore = {
chatMessages: [],
callbacks: [],
getAll: function() {
return this.chatMessages;
},
init: function() {
this.chatMessages = window.chat_messages.slice();
return this.emitChange();
},
create: function(message) {
this.chatMessages.push(message);
return this.emitChange();
},
emitChange: function() {
return this.callbacks.forEach(callback, function() {
return callback();
});
},
addChangeListener: function(callback) {
return this.callbacks.push(callback);
},
removeChangeListener: function(callback) {
return this.callbacks = _.without(this.callbacks, callback);
}
};
Hooking it up to a React Component
In your component you can now query the store for its data:
var Chat = React.createClass({
componentWillMount: function() {
return ChatMessageStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
return ChatMessageStore.removeChangeListener(this._onChange);
},
getInitialState: function() {
return this.getMessageState();
},
getMessageState: function() {
return {
messages: ChatMessageStore.getAll()
};
}
});
The Component registers a callback with the Store, which is fired on every change, updating the component and obeying the law of "don't modify state."

Backbone Collection is empty when testing with Jasmine

I'm just getting started with Jasmine and trying to set up some tests for the first time. I have a Backbone collection. I figured I would get my collection as part of the beforeEach() method, then perform tests against it.
I have a test json object that I used while I prototyped my app, so rather than mocking an call, I'd prefer to reuse that object for testing.
Here's my code so far (and it is failing).
describe("Vehicle collection", function() {
beforeEach(function() {
this.vehicleCollection = new Incentives.VehiclesCollection();
this.vehicleCollection.url = '../../json/20121029.json';
this.vehicleCollection.fetch();
console.log(this.vehicleCollection);
});
it("should contain models", function() {
expect(this.vehicleCollection.length).toEqual(36);
console.log(this.vehicleCollection.length); // returns 0
});
});
When I console.log in the beforeEach method -- the console look like this ...
d {length: 0, models: Array[0], _byId: Object, _byCid: Object, url: "../../json/20121029.json"}
Curiously when I expand the object (small triangle) in Chrome Developer Tools -- my collection is completely populated with an Array of vehicle models, etc. But still my test fails:
Error: Expected 0 to equal 36
I'm wondering if I need to leverage the "waitsFor()" method?
UPDATE (with working code)
Thanks for the help!
#deven98602 -- you got me on the right track. Ultimately, this "waitsFor()" implementation finally worked. I hope this code helps others! Leave comments if this is a poor technique. Thanks!
describe("A Vehicle collection", function() {
it("should contain models", function() {
var result;
var vehicleCollection = new Incentives.VehiclesCollection();
vehicleCollection.url = '/json/20121029.json';
getCollection();
waitsFor(function() {
return result === true;
}, "to retrive all vehicles from json", 3000);
runs(function() {
expect(vehicleCollection.length).toEqual(36);
});
function getCollection() {
vehicleCollection.fetch({
success : function(){
result = true;
},
error : function () {
result = false;
}
});
}
});
});
Just glancing at your code, it looks to me like fetch has not yet populated the collection when you run the expectation.
You can use the return value from fetch to defer the expectation until the response is received using waitsFor and runs:
beforeEach(function() {
this.vehicleCollection = new Incentives.VehiclesCollection();
this.vehicleCollection.url = '../../json/20121029.json';
var deferred = this.vehicleCollection.fetch();
waitsFor(function() { return deferred.done() && true });
});
it("should contain models", function() {
runs(function() {
expect(this.vehicleCollection.length).toEqual(36);
});
});
I haven't actually tried this can't guarantee that it will work as-is, but the solution will look something like this. See this article for more on asynchronous testing with Jasmine.
the collection.fetch() is asyn call that accepts success and error callbacks
var result;
this.collection.fetch({success : function(){
result = true;
}})
waitsFor(function() {
return response !== undefined;
}, 'must be set to true', 1000);
runs(function() {
expect(this.vehicleCollection.length).toEqual(36);
console.log(this.vehicleCollection.length); // returns 0
});

Resources