I've been facing a weird issue lately with my React App. I'm trying to parse a JSON object that contains arrays with data. The data is something like this:
{"Place":"San Francisco","Country":"USA", "Author":{"Name":"xyz", "Title":"View from the stars"}, "Year":"2018", "Places":[{"Price":"Free", "Address":"sfo"},{"Price":"$10","Address":"museum"}] }
The data contains multiple arrays like the Author example I've just shown. I have a function that fetches this data from a URL. I'm calling that function in componentDidMount. The function takes the data i.e responseJson and then stores it in an empty array that I've set called result using setState. In my state I have result as result:[]. My code for this would look something like this:
this.setState({result:responseJson})
Now, when I've been trying to access say Author Name from result I get an error. So something like this:
{this.state.result.Author.Name}
I'm doing this in a function that I'm using to display stuff. I'm calling this function in my return of my render function. I get an error stating :
TypeError:Cannot read property 'Name' of undefined. I get the same error if I try for anything that goes a level below inside. If I display {this.state.result.Place} or {this.state.result.Country} it's all good. But if I try,say {this.state.result.Author.Title} or {this.state.result.Places[0].Price} it gives me the same error.
Surprising thing is I've parsed this same object in a different component of mine and got no errors there. Could anyone please explain me why this is happening?
If I store the individual element while I setState in my fetch call function, I can display it. For example:
{result:responseJson,
AuthorName:responseJson.Author.Name
}
Then I'm able to go ahead and use it as {this.state.AuthorName}.
Please help me find a solution to this problem. Thanks in advance!
It could be that your state object is empty on the first render, and only updated with the data from the API after the request has completed (i.e. after the first render). The Name and Place properties don't throw an error, as they probably resolve to undefined.
Try putting an if block in your render method to check if the results have been loaded, and display a loading indicator if they haven't.
I'm guessing your initial state is something like this:
{ results: {} }
It's difficult to say without seeing more code.
[EDIT]: adding notes from chat
Data isn't available on first render. The sequence of events rendering this component looks something like this:
Instantiate component, the initial state is set to { results: [] }
Component is mounted, API call is triggered (note, this asynchronous, and doesn't return data yet)
Render method is called for the 1st time. This happens BEFORE the data is returned from the API request, so the state object is still {results: [] }. Any attempts to get authors at this point will throw an error as results.Authors is undefined
API request returns data, setState call updates state to { results: { name: 'test', author: [...] } }. This will trigger a re-render of the component
Render method is called for the 2nd time. Only at this point do you have data in the state object.
If this state evolves, means it is changed at componentDidMount, or after a fetch or whatever, chances are that your state is first empty, then it fills with your data.
So the reason you are getting this error, is simply that react tries to get this.state.result.Author.Name before this.state.result.Author even exists.
To get it, first test this.state.result.Author, and if indeed there's something there, then get Author.Name like this.
render(){
return(
<div>
{this.state.result.Author ? this.state.result.Author.Name : 'not ready yet'}
</div>
);
}
[EDIT] I'll answer the comment here:
It's just because they are at a higher level in the object.
this.state.result will always return something, even false if there is no result key in your state (no result key in your constructor for instance when the component mounts).
this.state.result.Country will show the same error if result is not a key of your state in your constructor. However, if result is defined in your constructor, then it will be false at first, then become the data when the new state populates.
this.state.result.Author.Name is again one level deeper...
So to avoid it, you would have to define your whole "schema" in the constructor (bad practice in my opinion). This below would throw no error when getting this.state.result.Author.Name if I'm not mistaken. It would first return false, then the value when available.
constructor(props){
super(props);
this.state={
result: {
Author: {}
}
}
}
Related
I have an pseudo-object is inside my state. I've been able to access through a couple layers, but when I reach the array inside the object Im getting undefined errors.
UPDATE: Its something wrong with how I pass lambdaReturnObject to the state which isn't letting me access the array, tested with lambdaReturnObject.campaigns[0].campaignName and it worked.
handleSearch() {
//data to use to query backend
let campaignId = this.refs.campaignInput.value
let marketplace = this.refs.marketplaceInput.value
//using local copy of backend data, production should call backend fo this instead
let lambdaReturn = "{\"advertiser\":{\"advertiserId\":\"1\",\"enforcedBudget\":0.1},\"campaigns\":[{\"campaignID\":\"1\",\"campaignName\":\"fake\",\"createDate\":11111,\"creationDate\":1111,\"startDate\":1111,\"endDate\":1111,\"dailyBudget\":0.1,\"internal\":{\"budgetCurrencyCode\":\"USD\",\"inBudget\":true},\"enforcedBudget\":0.1,\"budgetCurrencyCode\":\"USD\",\"budgetPacingStrategy\":\"asp\",\"budgetType\":\"averageDaily\",\"status\":\"enables\",\"internalStatus\":\"enabled\"}],\"campaignID\":\"1\"}"
let lambdaReturnObject = JSON.parse(lambdaReturn)
this.setState({
apiData: lambdaReturnObject
})
}
When I try and go to the array inside, I get the following error
<h3>Campaigns :{console.log(this.state.apiData.campaigns[0].campaignName)}</h3>
Cannot read property '0' of undefined
This means I am accessing it the wrong way, but I looked at other posts (Accessing Object inside Array) and I thought that this was right. Though I am definitely wrong or else I wouldn't be writing this.
JSON.parse() is synchronous function, so set state wont be called till, JSON.parse() executes completely and returns the object.
Still You can try following
Call JSON.parse() using a try-catch block like below and see if it works. Also it is error free way of parsing your stringified objects.
try {
let lambdaReturnObject = JSON.parse(lambdaReturn)
this.setState({
apiData: lambdaReturnObject
})object
}
catch (err) {
// Do error handling here.
}
Use optional chaining, and try to access your object like this.state.apiData.campaigns?.[0].campaignName; this won't give error even if compaigns is undefined.
Refer : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
Depending on what is happening. The call to get campaigns[0] is getting resolved before the API call finishes. You can try a promise or async await to make sure the object is retrieved from the API call before you attempt to access it.
Do you happen to have a code snippet of is being used to get the data?
The error was that render() calls right as the page is booted up. At that time the object is not stored in my state so trying to call
this.state.objects.innerObject.property
would fail because I declared objects in my state, innerObject could be counted as null before the object is actually loaded in so it wouldn't throw an error. But property would throw an error because innerObject as we know, is null.
Fix by using an if statement before rendering the page to see if the object is actually loaded in. If not render just default empty JSX.
I’m working on a solo project using React and I’ve been stuck on something for the past 2 days…I'm starting and I'm very beginner so it's maybe something very basic, but I'm struggling...
To try to be concise and clear:
I have a searchBar component, that searches through a local database, and returns objects associated with the search keyword. Nothing complicated so far.
Each rendered object has a button that triggers a function onClick. The said function is defined in my App component and is as follow:
changeState(term){
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{idToRender[0].recoInfo.push(response)})
})
this.setState({
objectToRender: idToRender
});
to explain the above code, what it does is that first, it identifies which object’s button has been clicked on, and send said object to a variable called idToRender. Then, it takes the reco state of that object, and store it to another variable called recoToFind. Then it calls the map() method on recoToFind, make an API request (the discogs() method) for each element of the recoToFind array and push() the results into the recoInfo state of idToRender. So by the end of the function, idToRender is supposed to look like this:
[{
…
…
recoInfo: [{1stAPI call result},{2ndAPI call result}…]
}],
The array contains 1 object having all the states of the object that was originally clicked on, plus a state recoInfo equal to an array made of the results of the several API calls.
Finally, it updates the component’s state objectToRender to idToRender.
And here my problem is, onClick, I do get all the states values of the clicked on object that get rendered on screen (as expected with how I coded the nested components), BUT, the values of the recoInfo are not displayed as expected (The component who’s supposed to render those values is nested in the component rendering the clicked on object other states values). However, they get displayed properly after a SECOND click on the button. So it seems my problem boils down to an state update timing trouble, but I’m puzzled, because this function is calling setState once and I know for a fact that the state is updated because when I click on the button, the clicked on Object details get displayed, but somehow only the recoInfo state seems to not be available yet, but only becomes available on a second click…
Would anyone have a way to solve this issue? :(
It somehow feels like my salvation lies in async/await, but I’m not sure I understand them correctly…
thanks very much in advance for any help!
Is this someting you want to do?
changeState(term) {
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{
idToRender[0].recoInfo.push(response)
this.setState({
objectToRender: idToRender
});
})
})
}
you can call setState once async call is done and result received.
This question already has answers here:
How to handle calling functions on data that may be undefined?
(2 answers)
Closed 3 years ago.
render() {
return (
<p>{this.state.recipes[0].title}</p> // This is what I believe should work - Ref Pic #1
// <p>{this.state.recipes[0]}</p> // This was trying random stuff to see what happens - Ref Pic #2
// <p>{this.state.recipes.title}</p> // This was me trying crazy nonsense - Ref Pic #3
)
}
I am attempting to traverse through some JSON and am getting some very wonky responses. If anyone would like to look at the JSON themselves, it's available at this link.
When the first p tag is run, I get the following response:
This is my first question, so I can't embed images, I'm sorry.
Being unsure why it said recipes[0] was undefined, I ran it again with the second p tag soley uncommented, to which I get the following response: Still the same question, still can't embed, sorry again.
That response really caught me off guard because the keys it reference's (nid, title, etc..) are the ones I know are in the object. 'Title' is what I want.
Last, I tried just the third p tag, to which the app actually compiled with no errors, but the p tag was empty. Here's what I have in React Devtools, but I still can't embed.
Looking at < Recipes > it clearly shows that state has exactly what I want it to, and image two shows recipes[0] has the key I try to call the first time. I have searched and Googled and even treated my own loved ones as mere rubberducks, but to no avail.
What am I doing wrong?
The error in the first p tag tells you what exactly is happening. Accessing a property of an 'undefined' will break javascript/ react.
You must probably not have the expected data in the state, to verify what I am saying, simply debug by console.log(this.state) in your render:
render() {
console.log(this.state); // probably has this.state.recipes empty array at first render
...
this probably happened because your state is being populated by an async call from an api. Render will be called first before your async request from an api resolves (which means unfortunately you don't have the ..recipes[0] data yet in your state)
I am guessing you have this in your componentDidMount:
componentDidMount() {
ThirdParty.fetchRecipes().then(data => {
this.setState({ recipes: data.recipes }); // after setState react calls render again. This time your p tag should work.
})
...
If you have newer react version I would recommend to use optional chaining because the property you are interested in is deeply nested: How to handle calling functions on data that may be undefined?
<p>{this.state?.recipes?.[0]?.title}</p>
Another effective way but too wordy:
const title =
this.state &&
this.state.recipes &&
this.state.recipes.length &&
this.state.recipes[0].title
? this.state.recipes[0].title
: "";
render() {return (<p>{title}</p>)}
I believe you are fetching the JSON asynchronously. In that case you should consider boundary cases where your initial recipes state is an empty array.
When you run the first p tag, since JSON is fetched asynchronously, initially this.state.recipes is not defined, hence you get the error.
When you run the second p tag, during the first render, this.state.recipes is undefined, hence it works inside p tag, but after the JSON is fetched and state is set, render is called again and this time this.state.recipes[0] is an object, hence it can't be counted as a valid react component, thus the error shows up.
With the 3rd p tag, as you have mentioned yourself, this.state.recipes.title will compile successfully with no errors, but the value will be undefined.
You just need to check for the edge cases where initial state is empty.
constructor(props) {
super(props);
this.state = { recipes: [] }
}
render() {
return (
<p>
{this.state.recipes.length > 0 ? {this.state.recipes[0].title} : ''}
</p>
)
}
you can do something like this to handle state when there is data and when there is no data
constructor(props) {
super(props);
this.state = {
recipes: []
};
}
render() {
return (
<p>
{this.state.recipes && this.state.recipes.length ? {this.state.recipes[0].title} : {this.state.recipes}}
</p>
)
}
I'm trying to access a property on an array that has on index 0 an object and I'm trying the following: object[0].main But the console throws me an error.
I'm using React on codepen so it doesn't show me the error pretty well, I want to access that property to be able to put it on another object that I'll pass down as props (because is something I hold on my state). Here is my code: https://codepen.io/manAbl/pen/aGymRg?editors=0011 Look for line 56.
This is stressing me out.
Thanks in advance
This happens because the data is not loaded from the server yet so the object will be initially as you set it to {}
so to fix this you can do
instead of
description: this.state.weather.weather[0].main,
do
description: this.state.weather.weather && this.state.weather.weather[0].main,
The idea is you set the initial state to : {} correct? and render is called before the ajax is actually finished since its async.
so on first render it will be "{}" and you are doing [0].main on it. which will crash.
I've made an AJAX call in React with Axios, and I'm a bit confused about how the response is dealt with.
Here is my code:
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then( res => {
const users = res.data
this.setState({ users });
});
}
render() {
console.log(this.state.users)
return (
<div>
{this.state.users.map( user => <p>{user.name}</p>)}
</div>
)
}
When I console.log the results, two values are returned:
[] - the empty array assigned to the initial state
The expected array from the API.
This presumably can be explained by the state value being returned once on initialisation, and then again with componentDidMount.
My problem arises in how I access the this.state.users value. Obviously any time I access this, I want the values returned from the AJAX response, but if, for example I try to render the name property from the first object in the array...
{this.state.users[0].name}
...it attempts to access [], and thus returns nothing.
However, if I try to iterate through the arrays elements...
{users.map( user => <p>user.name</p> )}
...it returns the values as expected.
I don't know why it works with the second example but not the first. Surely if the first is attempting to pull data from the initial state value (an empty array), then when mapping through this.state.users it would also be attempting to map through an empty array and return nothing, or an error.
I'm obviously not fully understanding the React rendering process here and how componentDidMount works in the component lifecycle. Am I wrong in trying to assign the response value directly to state?
Would appreciate if anyone could help to clear this up.
When you use map() on this.state.users initially, it will do so over an empty array (which won't render anything). Once the response arrives, and this.setState({ users }) is called, render() will run again. This time, the array of users is no longer empty and the map() operation will be performed on each of the users in the array.
However, when you use {this.state.users[0].name} initially on the empty array, you're trying to access the name property of an object that doesn't yet exist — which will result in an error. To prevent the error, you could do a check before accessing the properties of the object:
{this.state.users.length > 0 && this.state.users[0].name}
Now it will behave the same way as in the first example. I.e. the first render() won't actually do anything, since this.state.users.length = 0. But as soon as this.setState() is called with new users, render() will run again, and this time this.state.users[0].name is available.
There is another way with constructor. just add your state with empty array users as-
constructor(props) {
super(props);
this.state = {
users: []
};
}
With this component first rendered with empty users array. When you trigger state update, component rerender with new array.