I am making a GET request in my react app that returns team data. I am making this in the parent component and then passing it down to the child Loop component. The GET request is working but it is undefined when it gets to the render function, presumably because my GET request hasn't completed. I have tried to use componentWillMount but I was experiencing the same issue.
Is there anyone out there who can see something obvious i am doing wrong? i am quite new to React.
Please see my code below.
var App = React.createClass({
getInitialState: function() {
return {
teams: []
}
},
componentDidMount: function() {
$.get(url, function(data) {
this.state.teams.push(data)
});
},
render: function() {
console.log(this.state.teams)
return (
< div >
< Loop teams={this.state.teams} / >
< /div>
)
}
});
var Loop = React.createClass({
render: function() {
var teamList = this.props.teams.map(function(team, index){
return(
<h1 key={index}>
{team.name}
</h1 >
)
}) return <ul > {teamList} < /ul>
}
})
You're on the right track: the reason why you get undefined is because the request hasn't finished yet.
That's because your code uses this.state which changes the state directly. But your request is asynchronous so you need to use setState, which update state only when the request is done:
componentDidMount: function() {
$.get(url, function(data) {
this.setState({
teams: data,
})
}.bind(this));
}
See the docs.
Related
This one is kind of hard to explain, but basically when a click on a component, I make a get request for some data for another component. This however is only made after a couple of clicks.
I should probably also admit that I am not 100% sure if the place I am making the request is even correct, so if that's the case please let me know how I can get that fixed. Here's the code:
var ChangeLogData = React.createClass({
getInitialState: function () {
return {
content: {},
}
},
render: function () {
var _this=this;
$.get(this.props.source, function (data) {
var log = $.parseJSON(data);
_this.state.content = log;
}.bind(this));
return (
<div>
{_this.state.content[0]}
</div>
);
}
});
window.ChangeLog = React.createClass({
render: function () {
return (
<div>
<ChangeLogData name={this.props.params.name}
source={currentUrl + "/changelog/" +
this.props.params.name}/>
</div>
);
}
});
Edit: I should also probably add that it seems that most people recommend doing http requests on componentWillMount, but if I do that, the request only works once.
Edit 2: Here is the code of where the event is being called:
var AboutItem = React.createClass({
render: function () {
return (
<ListGroup>
{this.props.list.map(function (listValue,key) {
var link = currentUrl + "/changelog/" + listValue.split(' ')[0];
return <ListGroupItem key={key} className="module"
bsStyle="warning">
{listValue}
</ListGroupItem>
})}
</ListGroup>
);
}
});
I guess the idea is, the user will click on an item (that is dynamically generated), and when the item is clicked, it will send to the ChangeLog component the data in which it has to do the get request. So where exactly would I put my event handler?
I think the problem is that it's not being called correctly, as jquery is async...
var jqxhr = $.get( "example.php", function() {
alert( "success" );
})
.done(function() {
// PUT YOUR CALLBACK CODE HERE WITH THE RESULTS
})
.fail(function() {
alert( "error" );
})
.always(function() {
alert( "finished" );
});
And update the state in the .done()
You should not be making requests in your render method. You should also not be directly modifying state through this.state but instead use this.setState(). You also don't seem to be adding any onClick handlers.
You can do something like the following to trigger requests onClick:
var ChangeLogData = React.createClass({
getInitialState: function () {
return {
content: {},
}
},
handleClick: function() {
$.get(this.props.source, function (data) {
var log = $.parseJSON(data);
this.setState( { content: log } );
}.bind(this));
}
render: function () {
return (
<div onClick = { this._handleClick } >
{_this.state.content[0]}
</div>
);
}
If you want to do it on component mount you can put into componentDidMount() and call setState when you retrieve your data. This will cause the component to re-render with content
First: the get request is async, so by the time you get the response back the DOM is already rendered.
Second: Never update state inside the render method, if it works and you don't get an error message you most likely will create an infinite loop, render => updateState => render => udpateState...
you have multiple options, you can have the get request inside the function called after onClick (not shown in your code), and then update state and pass data as props. In this case you would be making a new get request every single time there's a click event. If you dont need a new get request on every click look into react lifecycle methods, in particular componentDidMount, which is basically executed after the react component is mounted, then you can do a get request there and update the state
componentDidMount: function() {
$.get(this.props.source, function (data) {
var log = $.parseJSON(data);
this.setState( { content: log } );
}.bind(this));
},
I can't see from your code what component should be clicked in order to trigger the request, but as far as could see, you should take the request out of the render() method. This method is called every time state/props change, so it might make your code make the request multiple times.
Another thing is that you should always mutate your state by calling this.setState() method, so in your case it would be _this.setState({ content: log }).
Now, if you change the name prop every time another component is clicked, you should do something like:
var ChangeLogData = React.createClass({
getInitialState: function () {
return {
content: {},
}
},
componentWillMount: function () {
this.getLog(this.props.source);
},
componentWillReceiveProps: function (nextProps) {
this.getLog(nextProps.source);
},
getLog: function (source) {
var _this = this;
$.get(source, function (data) {
var log = $.parseJSON(data);
_this.setState({
content: log
});
}.bind(this));
}
render: function () {
return (
<div>
{this.state.content[0]}
</div>
);
}
First you can extract the process to create the request call from the render() method into its own method, in this case, getLog(). Then, with two new methods from the React lifecycle, you can call getLog() when the component will be mounted and whenever new props come in from the parents components.
i'm currently working on a project with vue.js and vue-router. I got a vue in which I display some news, and I got thoses news from an API (it's kind of a blog).
I'm currently loading thoses news inside the router.data to set the data for the component, as said here. It's working great, no problems.
But my problem is that I want to animate the apparition of the news when I go to this view. I've tried using the ready property from the component, but it's called before the router.data has finished getting the news, which result in errors in animation, because there aren't any elements.
How can i trigger the animations once the news I fetch are fully rendered inside the DOM ?
Here is the code of my component:
export default {
name: 'News',
data: function () {
return {
news: []
}
},
route: {
data: function (transition) {
console.log('data hook')
return api
.getPostsByLimit(4, 1)
.then(function (posts) {
for (var i = 0; i < posts.length; i++) {
var post = posts[i]
post.formatedDate = moment(post.date).format("D MMM. YYYY")
post.dateTime = moment(post.date).format("YYYY-MM-DD")
if(post.news_artist_related) {
post.news_artist_related = JSON.parse(post.news_artist_related)
post.news_artist_related.type = slugify(post.news_artist_related.type)
post.news_artist_related.slug = slugify(post.news_artist_related.slug)
}
}
return posts
})
.then(news => ({news}))
}
},
ready: function () {
console.log('Ready hook')
animateNewsApparition()
}
}
From the docs:
When resolved, the component will also emit a 'route-data-loaded' event.
So:
events: {
'route-data-loaded': function() {
animateNewsApparition()
}
}
I am trying to build a simple blog with React, Express, MongoDB and Node. But I am still confused on (1) how to correctly make the ajax request to my database and how do I set state and (2) how to properly update the state.
I've tried setting getInitialState by making an AJAX request, but it won't work. Also I don't know if that is best practice. Also, once someone adds a new post, where am I supposed to place the POST and then how do I properly update state?
var React = require('react');
var List = React.createClass({
render: function() {
return (
<div>
<h2>{this.props.postbody}</h2>
</div>
)
}
})
// I'll break this up into smaller components later, but for now I just want
// to know where to put my database entries into the posts array.
// The only field in MongoDB right now is postbody.
var Home = React.createClass({
getInitialState: function() {
return {
posts: []
}
},
handleClick: function() {
$.ajax({
type: 'GET',
url: '/api/blogPosts',
success: function(data) {
this.setState = data;
console.log(this.setState);
}
})
},
render: function() {
return (
<div>
{this.state.posts.map(function(post) {
return (
<List postbody={post.postbody}></List>
)
})}
</div>
)
}
})
setState is a function, not a property to be set on this. You should do this.setState(data)
Using reactjs:react as the official react package isn't installing correctly for Windows yet.
Just trying to get to grips with React and getting pretty frustrated by what seem to be small things. For some reason I can't actually query any of my Mongo collections via my React components - the basic Mongo queries in the Chrome console work as expected...
var ExampleComponent = ReactMeteor.createClass({
getInitialState: function() {
return {data: []};
},
//didn't think the following was necessary, but tried it to no avail:
startMeteorSubscriptions: function() {
Meteor.subscribe('exampleCollection');
},
componentDidMount: function() {
var collection = ExampleCollection.find().fetch();
console.log(collection); //Empty Array []
console.log(ExampleCollection); //Mongo Collection
console.log(ExampleCollection.find()); //Cursor
console.log(ExampleCollection.find().fetch()); //[]?? wtf?
this.setState({data: collection});
},
render: function() {
return (
<div data={this.state.data}>
Hello World?
</div>
);
}
});
Meteor.startup(function() {
React.render(<ExampleComponent />, document.getElementById('root'));
})
So what's going on here? Any help would be appreciated, I'm not finding as many resources about doing the basics with React and Meteor that I had hoped.
In reactjs:react, you need to implement a method: getMeteorState()
This is what sets your data to be available in your component when render() is called. You still should implement startMeteorSubscriptions if you're doing pub/sub with your data (which you did correctly).
For example:
var ExampleComponent = ReactMeteor.createClass({
// Methods specific to ReactMeteor
startMeteorSubscriptions: function() {
Meteor.subscribe('exampleCollection');
},
getMeteorState: function() {
return {
data: ExampleCollection.find().fetch()
};
},
// React Methods
getInitialState: function() {
return {};
},
render: function() {
var data = this.state.data;
return (
<div>
{/* Present your data here */}
</div>
);
}
});
I've just figured out that object in React's state that have multiple children cannot be rendered easily.
In my example I have component which speaks with third-party API through AJAX:
var Component = React.createClass({
getInitialState: function () {
return {data: {}};
},
loadTrackData: function () {
api.getDataById(1566285, function (data) {
this.setState({data: data});
}.bind(this));
},
componentDidMount: function () {
this.loadTrackData();
},
render: function () {
return (
<div>
<h2>{this.state.data.metadata.title}</h2>
</div>
);
}
});
The problem is that {this.state.data.metadata} renders fine..
But {this.state.data.metadata.title} throws error Uncaught TypeError: Cannot read property 'title' of undefined!
What is the proper way to deal with such async data?
I always like to add the loading spinner or indicator if the page has async operation. I would do this
var Component = React.createClass({
getInitialState: function () {
return {data: null};
},
loadTrackData: function () {
api.getDataById(1566285, function (data) {
this.setState({data: data});
}.bind(this));
},
componentDidMount: function () {
this.loadTrackData();
},
render: function () {
var content = this.state.data ? <h2>{this.state.data.metadata.title}</h2> : <LoadingIndicator />;
return (
<div>
{content}
</div>
);
}
});
with the loading indicator basically it improve the user experience and won't get much of unwanted surprise. u can create your own loading indicator component with lots of choices here http://loading.io/
this.state.data.metadata is undefined until loading occurs. Accessing any property on undefined gives you a TypeError. This is not specific to React—it's just how JavaScript object references work.
I suggest you use { data: null } in initial state and return something else from render with a condition like if (!this.state.data).