React: initial render fires before state is set (via ajax) - javascript

I've set things up so the HomePage component renders UserShow for the current logged in user. For example, if a user with an ID of 2 is logged in and visits the HomePage page, it will render their UserShow.
The "normal" UserShow works correctly. For example if you type in /users/18, it will properly render. However it's not working when HomePage renders it.
I'm new to React (especially its lifecycle methods), so my debugging has been to throw alerts in at various steps. I'd say the most important findings to share are:
currentUserID( ) is functioning and returns the correct ID
Hard-coding the value of state.userID within componentDidMount causes things to work correctly
These two points lead me to believe that Render is being called before it can update state.userID with its (correct) return value. Even more specific is that it's rendering before the .success portion of the this.currentUserID() ajax call returns. If this is so, what's the best way to go about not doing an initial render until an ajax call like this completes?
My code is in a state of spaghetti - it's my first time doing front-end routing with JavaScript. I'm also managing sessions via using the user's email as the token in localStorage - I'm new to sessions in JS as well. Please bear with me.
HomePage component:
var HomePage = React.createClass({
getInitialState: function(){
return{
didFetchData: false,
userID: null,
}
},
componentWillMount: function(){
newState = this.currentUserID()
this.setState({userID: newState})
// this.setState({userID: 2}) //hard-coding the value works
},
currentUserID: function(){
if(App.checkLoggedIn()){
var email = this.currentUserEmail()
this.fetchUserID(email)
}else{
alert('theres not a logged in user')
}
},
currentUserEmail: function(){
return localStorage.getItem('email')
},
fetchUserID: function(email){ //queries a Rails DB using the user's email to return their ID
$.ajax({
type: "GET",
url: "/users/email",
data: {email: email},
dataType: 'json',
success: function(data){
this.setState({didFetchData: 'true', userID: data.user_id})
}.bind(this),
error: function(data){
alert('error! couldnt fetch user id')
}
})
},
render: function(){
userID = this.state.userID
return(
<div>
<UserShow params={{id: userID}} />
</div>
)
}
})
UserShow component:
var UserShow = React.createClass({
getInitialState: function(){
return{
didFetchData: false,
userName: [],
userItems: [],
headerImage: "../users.png"
}
},
componentDidMount: function(){
this.fetchData()
},
fetchData: function(){
var params = this.props.params.id
$.ajax({
type: "GET",
url: "/users/" + params,
data: "data",
dataType: 'json',
success: function(data){
this.setState({didFetchData: 'true', userName: data.user_name, userItems: data.items, headerImage: data.photo_url})
}.bind(this),
error: function(data){
alert('error! couldnt load user into user show')
}
})
},
render: function(){
var userItem = this.state.userItems.map(function(item){
return <UserItemCard name={item.name} key={item.id} id={item.id} description={item.description} photo_url={item.photo_url} />
})
return(
<div>
<Header img_src={this.state.headerImage} />
<section className="body-wrapper">
{userItem}
</section>
</div>
)
}
})

So what you want to do is to avoid rendering anything until your ajax-request returns your result.
You can do a check in the render method if the state is how you want it. If it's not, then return null, or a loader or some other markup. When the componentDidMount then sets the state, it will trigger a re-render, since the userID then is set, it will return the userShow component
Example:
render(){
if(this.state.userID === null){
return null; //Or some other replacement component or markup
}
return (
<div>
<UserShow params={{id: userID}} />
</div>
);
}
Fetching the data in the userShow component could be done like this:
componentWillReceiveProps(nextProps){
//fetch data her, you'll find your prop params in nextProps.params
}
You can also avoid doing this here, by kicking the data-fetch in the render-method.

Your initial data set what your doing in componentWillMount will not help to set data because you want to fetch data from ajax. Debug fetchUserID and
currentUserID function whether your getting correct email from localstorage and user id from server. Others is fine.

Related

Asyncronous setState not working in componentDidMount, but working elsewhere: React Native

My plan is to get an array of strings from a http request with an authorization token in the header, and then use the array to decide what to render(multiple components means the user is an admin hence he ll be sent to a listView e.t.c)
How I store the authorisation token: (react-native-router-flux is used for navigation)
AsyncStorage.setItem('access_token',token).then((value)=> { Actions.pageTwo()}).done()
//navigates to second page
In the second page have
var HotelAdminService = require('./services.js');
....
constructor(props) {
super(props);
this.state={
keyLoaded:false,
};
this.tester= HotelAdminService.tester.bind(this);
}
componentDidMount() {
HotelAdminService.tester.bind(this);
}
render() {
console.log(this.state);
if (!(this.state.keyLoaded) ) {
return this.renderLoadingView();
}
return (
<View style={styles.viewContainer} >
<SearchBar onChangeText={(e) => this.clickMovie(e)}> </SearchBar>
<ListView dataSource={this.state.dataSource} renderRow= {this.renderMovie} style={styles.listView}/>
</View>
);
}
services.js:
exports.tester=function(){
let REQUEST_URL= config.baseUrl + 'api/HotelAdmin/GetKey';
AsyncStorage.getItem('access_token').then((value) => {
fetch(REQUEST_URL, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + value,
}
}).then((response) => {return response.json()} ).then((responseData) => {
this.setState(
{key: responseData,
keyLoaded: true,
dataSource: this.state.dataSource.cloneWithRows(responseData),
});
}).done();
});
}
Now I know this method works, as it does what I want it to do, however only when I call it from a button click from the second page. What I want to do however is to get the HotelAdmin.tester work at the begining so it can lead to a re-render when I get the data.As you can see My render method gives a loading screen until keyLoaded becomes true. Maybe one alternative would be the tester method to return a string instead of changing the state, however I was unable to return a string array from the nested promise calls.
In its current condition, the code stays stuck in the loading screen as HotelSystemAdmin.tester won't run and change the state
You are only binding the function to a context in componentDidMount. You'll need to invoke it:
HotelAdminService.tester.bind(this)();

Reactjs: Loading view based on response

Looking for a way for React to process some json, and load views based on the response.
For example:
1- React has a form, response goes out to external API
2- API processes the input, returns a success code unless there was validation issues, and send a response back to the React app
3- React gets the json response, loads a "Success" view, or reloads the form and outputs the erros
Is there a simple way for React to handle this? Thanks in advance!
Very simple...
Basically, you need to track when you initiate request (sending data) and when request is completed (receiving response).
Based on data returned, you decide what to render...
Take a look at this example (working fiddle)
// In this example, we are using JSONPlaceholer service do real
// request and receive response;
const root = 'http://jsonplaceholder.typicode.com';
const Success = () => (<div>Success!</div>);
const Error = () => (<div>Error! Fix your data!</div>);
const Form = React.createClass({
getInitialState() {
return {
processing: false,
result: undefined,
};
},
submit(event) {
this.setState({ processing: true });
event.preventDefault();
fetch(`${root}/posts`, {
method: 'POST',
data: {
// your data here...
}
})
.then(response => {
// We simulate succesful/failed response here.
// In real world you would do something like this..
// const result = response.ok ? 'success' : 'error';
const processing = false;
const result = Math.random() > 0.5 ? 'success' : 'error';
this.setState({ result, processing });
});
},
render() {
const { result, processing } = this.state;
if (result === 'success')
return <Success />;
return (
<form>
Form content here<br/>
<button onClick={this.submit}>
{ processing ? 'Sending data...' : 'Submit' }
</button>
{ result === 'error' && <Error /> }
</form>
);
},
});
render(<Form />, document.getElementById('root'));
The easy way would be to trigger the new state with setState() from the API callback function such as in the example below, although I recommend using a library such as Redux for state management.
var MainComp = React.createClass({
getInitialState: function() {
return {someProp: ""}
},
callAPI: function() {
var root = 'http://jsonplaceholder.typicode.com';
$.ajax({
url: root + '/posts/1',
method: 'GET'
}).then(function(data) {
this.setState({someProp: data.body})
}.bind(this));
},
render: function(){
return (
<div>
<h2>{this.state.someProp}</h2>
<button onClick={this.callAPI}>Async</button>
</div>
)
}
});
React.render(<MainComp/>, document.getElementById("app"));
Please note this is a naive example, you should still cover up error cases and build a logic to trigger different views based on state.

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)

Result of Multiple Ajax requests, one depends on other Ajax Output

I am developing a web application on Perl Catalyst and using ReactJS for the view, jQuery for AJAX, and JSX added as a script in the header.
I am able to fetch AJAX JSON data on a page and refresh the data every 10 seconds. We found this option we create on load on server to fetch data on every 10 seconds. This application is going to be used by a number of users together, so we autogenerate a key which will be incremented if any data is updated on that database table. We set this key on the rest, and it can be accessed by AJAX JSON.
I want to implement a React component which will check this autogenerated AJAX JSON key and will compare it to its previous value every 10 seconds. If they are not equal then it will call the other AJAX function or React component which will update the data on the view page, and older values will be replaced by new.
I have searched a lot but don't get the logic to implement this in ReactJS. Any logic or reference link will be helpful.
Think about the "Refresh_token" component as your controller. It will handle all of the checking token and get the new order when token has changed. The "Order list" should not know when a token has changed, its job is to re-render its view when a new list has arrived.
//Section 1 Starts Here
var Orderlist = React.createClass({
render: function(){
var orderlist11 = [];
for(var i = 0; i < this.props.list.length; i++){
var orderItem = this.props.list[i];
orderlist11.push(
<div className="card">
<div className="title">
Order Number {orderItem.ordernumber}
</div>
<div className="content">
Date & Time : {orderItem.bizorderdate} <br />
Username : {orderItem.userid}
</div>
</div>
);
}
return (
<div> {orderlist11} </div>
);
}
});
////// Section 1 ends here
var RefreshToken = React.createClass({
getInitialState : function(){
return {
token : -1 , //Or anything that is not a token.
orderlist : []
}
},
refreshTimer : null,
componentDidMount : function(){
this.get_order_list(); //Get the intial order list
this.refreshTimer = setInterval(this.refresh_token, 10000); //Call to check the new token every 10s
},
refresh_token : function(){
$.ajax({
type: 'GET',
url: "/order/refresh",
headers: {
Accept : "application/json","Content-Type": "application/json"
},
success: function (resp){
var newToken = resp;
console.log(newToken); //it give the value of refresh eg. ["20150925313"] //Assume this will give the new value of the order list changed.
if(newToken != this.state.token){
this.setState({ token: newToken});
this.get_order_list() //Get the new list when token has changed.
// console.log(this.state.resp);
}
}.bind(this)
});
},
get_order_list : function(){
$.ajax({
type: 'GET',
url: "/order/list/10/1",
headers: {
Accept : "application/json", "Content-Type": "application/json"
},
success: function(orderlist) {
// console.log(orderlist);
// console.log(this.state.orderlist);
this.setState({orderlist: orderlist});
}.bind(this),
error: function(xhr, status, err) {
}
});
},
render: function(){
return <Orderlist list={this.state.orderlist} />
}
});
React.render(
<RefreshToken />,
document.getElementById('list_of_orders')
);

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