React component get request being made in one or clicks late - javascript

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.

Related

react-http-request does not change loading state after second request

I am now using react-http-request in my React.js component to send request and process the response. The URL parameter passed is relevant to the component state such that when the state changes, the component will be re-rendered and change the component display.
This works on the first request. However, I found that the component does not return a {load: true} after the second request, and I wonder how to solve this.
I tried to call the onRequest method and set the loading state for the component, but I cannot change the loading state after the request is finished (as render function cannot change the state).
react-http-request: https://github.com/mbasso/react-http-request
My Code is like below:
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType
}
},
// ... details emitted.
render: function(){
return (<Request
url={config.url.api + "/" + this.state.queryType}
method="get"
accept="application/json"
query={{ several parameter }}
>
{
({error, result, loading}) => {
if (loading || error) {
return <Loading />
}
else {
// process the result here.
}
}
}
</Request>)
}
So, my initial recommendation would be that you use some state management library (redux, mobx, etc) but it is not necessary to get a working example of your code, so:
import fetch from 'whatwg-fetch'; // gives compatibility with older browsers
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType,
content: null
}
},
componentWillMount: function() {
this.fetchContent();
},
fetchContent: function() {
const uri = config.url.api + "/" + this.state.queryType;
// You can use w/e you want here (request.js, etc), but fetch is the latest standard in the js world
fetch(uri, {
method: 'GET',
// More properties as you see fit
})
.then(response => response.json()) // might need to do this ;)
.then(response => {
this.setState({
content: response
})
})
},
// ...
render: function(){
const content = this.state.content? (
// render your content based on this.state.content
): (
<Loading />
)
return content;
}
});
Haven't tested this code, but there are some nice benefits to it:
The http request is not dependant on React, which should (in theory) be for UI components.
The fetching mechanism is decoupled, and can be re-used at any point in the component lifecycle
In my opinion easier to read, divided into smaller logical chunks
I would recommend reading the React Component Lifecycle.
In this case, I read the source code of the react-http-request, and found that there is a weakness that after accepting and sending the second request, the component failed to update the state of "loading" returns.
// starts from Line 49
value: function componentWillReceiveProps(nextProps) {
if (JSON.stringify(this.props) === JSON.stringify(nextProps)) {
return;
}
this.request.abort();
this.performRequest(nextProps);
}
Manually changed the state of loading here can help reset the loading after each request received.
I changed the source code of this lib, and sent the pull request to the repo. It's now merged into master and ejected a new release.
See: https://github.com/mbasso/react-http-request/pull/3
Thus, this problem can be solved by keeping the lib update to the release (currently it is 1.0.3).

using data from GET request in react and use in child component

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.

How do I pull items from MongoDB and update them when a new one is entered?

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)

react.js - Deep Object in state with async data does not work

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).

ReactJS event-driven updates on parent component

I have some components that are buried deep inside their parents. I have a user variable, which is fetched at the absolute top of this chain (a ProfilePage component), and passed all the way down with props. But, sometimes the user updates its profile in a small component on the page (the AboutMe component for example). My current flow is to have the AboutMe component update the user via ajax, and then trigger an event called 'profileupdate' on my pubsub. When 'profileupdate' is triggered, the ProfilePage fetches the user again via ajax, and so all the props are updated. This tactic appears to work, but it almost always gives:
Uncaught Error: Invariant Violation: replaceState(...): Can only update a mounted or mounting component.
I know this error is coming from the ProfilePage not being mounted and replacing its user state.
Here is an example of a place where I do this:
In the ProfilePage component:
var ProfilePage = React.createClass({
getInitialState: function() {
return {};
},
updateUser: function() {
this.setUser('/api/users/' + this.props.params.userId);
},
setCurrentUser: function(currentUser) {
this.setState({ currentUser: currentUser });
},
resetCurrentUser: function() {
auth.getCurrentUser.call(this, this.setCurrentUser);
},
componentWillReceiveProps: function(newProps) {
this.setUser('/api/users/' + newProps.params.userId);
},
componentDidMount: function() {
PubSub.subscribe('profileupdate', this.updateUser);
PubSub.subscribe('profileupdate', this.resetCurrentUser);
this.resetCurrentUser();
this.updateUser();
},
setUser: function(url) {
$.ajax({
url: url,
success: function(user) {
if (this.isMounted()) {
this.setState({ user: user });
} else {
console.log("ProfilePage not mounted.");
}
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.userId, status, err.toString());
}.bind(this)
});
},
Then, in the AboutInput component:
ProfilePage --> ProfileBox --> About --> AboutInput
updateAbout: function(text) {
var url = '/api/users/' + this.props.user._id;
var user = this.props.user;
user.about = text;
$.ajax({
url: url,
type: 'PUT',
data: user,
success: function(user) {
auth.storeCurrentUser(user, function(user) {
return user;
});
PubSub.publish('profileupdate');
this.propagateReset();
}.bind(this),
error: function(xhr, status, err) {
console.error(status, err.toString());
this.propagateReset();
}.bind(this)
});
},
The takeaways here are that the ProfilePage fetches the user again when the profileupdate event is triggered.
Am I using the wrong tactics here? How can I handle this kind of update? And, can I force the ProfilePage to become mounted? That would be really ideal. Also, interestingly, whether or not the console logs that the ProfilePage is unmounted, the user still updates.
I really would take a look at Facebook's FLUX implementation. It will simplify your workflow tremendously. I am guessing that is not an option at this moment, but make sure your components are unsubscribing when removing them. For eg:
componentWillUnmount: function () {
var self = this;
self.store.removeListener('change', self._setState);
self._setState = null;
}
Or in your case unsubscribe using the method above from your PubSub implementation when a component is being replaced. So 'profileupdate' is being called on a React component that has been unmounted. Something like.
componentWillUnmount: function () {
PubSub.unsubscribe('profileupdate', this.updateUser);
PubSub.unsubscribe('profileupdate', this.resetCurrentUser);
this.updateUser = null;
this.resetCurrentUser = null;
}
It is exactly what the error states. You trying to set state on a React component that is not longer mounted, or has been removed.

Categories

Resources