Ok, so I'm building out a custom API in React. When I make the calls, I'm getting JSON data back and store that into local storage with JSON.Stringify:
localStorage.setItem('user', JSON.stringify(response.data))
Later, I call this item onto the Homepage to return some of that data once the user is logged in using:
var user = JSON.parse([localStorage.getItem('user')])
This returns the object:
{
"OrderId":0,
"IsLoggedIn":true,
"ModeOfSaleId":64,
"OriginalModeOfSaleId":64,
"SourceId":8580,
"LoginInfo":{"ConstituentId":190554,"OriginalConstituentId":190554,"UserId":"test#email.org","Status":"P","FailedAttempts":0,"LockedDate":null,"ElectronicAddress":"test#email.org"},
"CartInfo":{"PerformanceCount":0,"PackageCount":0,"ContributionCount":0,"MembershipCount":0,"UserDefinedFeeCount":0,"GiftCertificateCount":0,"PaymentCount":0,"FirstSeatAddedDateTime":null},
"BusinessFacing":false,
"IsGuest":false,
"CheckoutStatus":{"Status":"No Checkout","Date":null},
"HasLockedSeats":false,
"SeatsExpired":false
}
The Issue:
Un-nested properties return normally {user.OrderId} or {user.ModeOfSaleId} However, trying to return the nested values like {user.LoginInfo.ConstituentID} result in the error:
Uncaught TypeError: Cannot read property 'ConstituentId' of undefined
Returning {user.LoginInfo} actually returns an object, but obviously, can't print that to a string. Returning {user.LoginInfo["ConstituentId"]} results in the error:
Uncaught TypeError: Cannot read property 'ConstituentId' of undefined
So yeah, I'm stumped, I don't know how I'm returning this incorrectly. Any help is appreciated.
This code works for me:
localStorage.setItem("user", JSON.stringify({
"OrderId":0,
"IsLoggedIn":true,
"ModeOfSaleId":64,
"OriginalModeOfSaleId":64,
"SourceId":8580,
"LoginInfo":{"ConstituentId":190554,"OriginalConstituentId":190554,"UserId":"test#email.org","Status":"P","FailedAttempts":0,"LockedDate":null,"ElectronicAddress":"test#email.org"},
"CartInfo":{"PerformanceCount":0,"PackageCount":0,"ContributionCount":0,"MembershipCount":0,"UserDefinedFeeCount":0,"GiftCertificateCount":0,"PaymentCount":0,"FirstSeatAddedDateTime":null},
"BusinessFacing":false,
"IsGuest":false,
"CheckoutStatus":{"Status":"No Checkout","Date":null},
"HasLockedSeats":false,
"SeatsExpired":false
}));
const user = JSON.parse(localStorage.getItem("user"));
console.log(user.LoginInfo.OriginalConstituentId);
What about using spread operator to get what you want?
const user = JSON.parse(localStorage.getItem('user'))
const { ConstituentId, UserId } = user.LoginInfo
console.log(ConstituentId) // 190554
Ok, so the way I'm returning these values seems to be an "issue" because of the way React handles it's Render event. When I'm pulling in the data on the componentDidMount() event, a Render event still fires just before this.
componentDidMount() {
this.setState({
user: JSON.parse([localStorage.getItem('user')]),
users: { loading: true }
});
}
So in the Render event:
render() {
const { user, users, loading } = this.state;
var { ConstituentId, UserId } = user.LoginInfo
return (
<div className="col-md-6 col-md-offset-3">
<h1>Hi!</h1>
<p>{UserId}</p>
<p>You're logged in with React & Basic HTTP Authentication!!</p>
<h3>Users from secure api end point:</h3>
<p>
<Link to="/login">Logout</Link>
</p>
</div>
);
}
It fires TWICE, once BEFORE the state.user is set by componentDidMount() and once again after. So, my code was erroring out because of the first firing of Render, when nothing was set, hence the undefined message. I figured out how to bypass this with checking the login info object is returning as typeof object. This is in my render event:
var result = (typeof user.loginInfo === 'object');
if (result && loading) {
console.log(result)
console.log(user.LoginInfo.ConstituentId)
var { ConstituentId, UserId } = user.LoginInfo
}
But that's not very elegant. So, ultimately I re-wrote how I was handling unloaded information in componentDidMount() by creating a state prop called 'loading':
this.state = {
loading: true,
user: {}
};
In componentDidMount() I'm doing this:
this.setState({
user: JSON.parse(localStorage.getItem('user')),
loading: false
});
And in render():
const { loading, user } = this.state;
if (!loading) {
var { ConstituentId, UserId } = user.LoginInfo
}
console.log(ConstituentId)
It works great!
Basically, I'm just waiting for componentDidMount() to fire using the loading state by setting it to false in the function. Then we know it's loaded and can successfully render the data.
Is it possible to set the ternary operator and call the start() function. Under the influence of data from API, pending will change totrue.
I am trying to use the ternary operator inside the button and initiate the click event. Pending = true call the click event, thestart ()function.
{this.state.pending ? this.start() : null}
class Button extends Component {
constructor() {
super();
this.state = {
isRunning: false,
pending: false
};
}
componentDidMount() {
axios.get
axios({
url: "https://app/api/v1/running",
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
console.log(response);
this.setState({
pending: response.data.pending //true
});
})
.catch(error => {
console.log(error);
})
}
start = () => {
console.log('AAAA');
}
render () {
return (
<button {this.state.pending ? this.start() : null} onClick={this.start}>
Start
</button>
);
}
}
You could take one of two approaches here.
1. Run the function straight from the api response success callback
.then(response => {
console.log(response);
this.setState({
pending: response.data.pending //true
});
this.start(); //Here
})
If you want to update the state before calling that function in any case then simply call the function in the callback of setStates second parameter. setState also accepts a function as its parameter and executes that function after performing the update. This is important to remember because setState operates asynchronously. So, If you want to make sure before your call the value has to update for some reason you can do this way. More about it here: https://reactjs.org/docs/react-component.html#setstate
.then(response => {
console.log(response);
this.setState({
pending: response.data.pending //true
}, this.start);
})
2. Use a component lifecycle method to listen for state changes and run the function
componentDidUpdate = (prevProps, prevState) => {
if(prevState.pending === false && this.state.pending === true){
this.start()
}
}
Every time you have a state or prop change componentDidUpdate executes. Note that it does not update when the component first mounts. And from the look of your code since pending is local to this component only you don't need to capture the case when it mounts firsts. More reference on lifecycle methods: https://reactjs.org/docs/react-component.html
Hope that clears your confusion here. Cheers!
I wrote a method to handle adding a "like" on click. It's supposed to toggle isLiked to the opposite of the previous state and increment likes count by 1.
I think the error might be due to conflicting states but I'm not sure how to verify this.
addLike = postId => {
this.setState({
posts: this.state.posts.map(post => {
if (post.id === postId) {
return {
...post,
likes: post.likes + 1,
isLiked: !post.isLiked
};
}
return {
post
};
})
});
};
I was expecting like to increment by 1 and isLiked to be set to true. However, on click I'm getting an error that it "cannot read property of undefined".
You can code defensively against potentially missing properties:
if (post && post.id === postId) { // is it possible your posts array has `undefined` or `null` entry in it?
return {
...post,
likes: post.likes + 1;
isLiked: !post.isLiked
};
...
And elsewhere, where trying to access a property on a null or undefined object could throw that exception. For example, is state.posts properly populate or at least initialized to an empty array []? If not, this.state.posts.map could also throw the same error.
Furthermore, use React DevTools extension to inspect your state and check the values.
If your setState should rely on the previous state, then better go with the syntax with updater function. Something like
this.setState(({posts, ...restOfTheState}) =>
({
posts: posts.map( // mapping logic here
...restOfTheState,
});
BTW, state.posts was initialized, right?
setState user property inside removeListener return undefined when i console.log() it inside component but when i check state in react developer tool user object from firebase it is there with actual value I want
state = {
coinList: {},
fav: [],
currentFavourite: '',
prices: [],
authed: false,
user: null
};
componentDidMount = () => {
this.removeListener = firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({
authed: true,
user
});
} else {
this.setState({
authed: false
});
}
});
this.ref = base.syncState('fav', {
context: this,
state: 'fav',
asArray: true
});}
If your console.log statement is inside the removeListener, I'd suspect that state hasn't been updated by the time console.log is called.
setState is asynchronous, so it's been updated in the background whilst the next statements are being.
You can provide setState with a function or statement that is only called once setState is completed....
this.setState({ user }, this.someFunction())
or simply...
this.setState({ user }, console.log(this.state.user));
I'm building a blog application that has an articles index page, and from there you can click on an article to see the article or edit the article.
If you're going from the index page to the edit page, it works just fine because I already have all the articles in state. But if I refresh after I've gone to the edit-article page, I no longer have all the articles in state.
This is a problem because I'm making an asynchronous recieveSingleArticle call in the componentDidMount of my edit-article page, then I setState so my form is prepopulated. There's a double render which causes an "Uncaught TypeError: Cannot read property 'title' of undefined" error, presumably during the first render before the article has been received into state.
class ArticleEdit extends React.Component {
constructor(props) {
super(props);
this.state = {title: "", body: "", imageFile: ""};
this.handleChange = this.handleChange.bind(this);
this.handlePublish = this.handlePublish.bind(this);
this.handleFile = this.handleFile.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
componentDidMount() {
const { article, requestSingleArticle } = this.props;
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
...
I tried wrapping my async calls inside of an "if (this.props.article)" but that didn't work. Is there a best way of dealing with this type of problem? Any advice greatly appreciated!
UPDATE:
Another solution that works is to have a componentDidUpdate in addition to componentDidMount. check in componentDidMount if this.props.article exists and if so, setState. And in componentDidUpdate, wrap the setState in the following conditional:
if (!prevProps.article && this.props.article)
Just check if the article is present in the props before calling async action
componentDidMount() {
const { article, requestSingleArticle } = this.props;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
Since you are not getting any render from this method , it means that the props are not yet obtained in the life cycle method componnetDidMount. So instead you can use componentWillReceiveProps like this
componentWillReceiveProps(nextProp) {
// this line here will check the article props' status so that
// we will not use setState each time we get a prop
if (this.props.article === nextProp.article) return;
// rest is just the same code from above
const { article, requestSingleArticle } = nextProp;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}