Attempt to render state value from ajax call in componentDidMount failing (React) - javascript

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.

Related

React - Unable to setState inside a Promise then

I'm trying to set the state inside a then() of a Promise, but the state value is not getting saved and is not accessible outside of the then().
Below is the UPDATED code:
handleSelect = location => {
this.setState({ location });
geocodeByAddress(
location
)
.then(results => getLatLng(results[0]))
.then(latLng => {
this.setState({
location_latitude: latLng.lat,
location_longitude: latLng.lng,
})
console.log(this.state.location_latitude); // returns the correct value
})
console.log(this.state.location_latitude); // returns null (the originally declared value)
}
If I console.log(this.state.location_latitude) I get a null value. But if I console.log inside the then() block, I get the correct value. How do I set the state in this case?
UPDATED explanation:
To give a better context to the whole logic here: I'm using a Google Places Autocomplete component which allows the user to select a location from the dropdown. The above method handleSelect is called when the user selects a location from the dropdown. I then extract the latitude and longitude of the place selected by the user using the geocodeByAddress method, which is a promise. I need to then retrieve the latitude and longitude and set the state accordingly, which is later used to send to the database. I can't submit the value to the database yet because there are other form inputs that the user needs to fill (which are also stored in the state) and once he clicks on Submit, I'll be submitting all the state elements in a single call to the backend. So, there is nothing I want to update with componentDidMount() or nothing is getting updated yet to use componentDidUpdate(). In any case, I can only store the state value inside the then of geocodeByAddress (please correct me if I'm wrong here). So, I HAVE to be able to modify the state value inside the then block.
The thing is when you set-state you can't see the state result immediately after that. That's why the console.log returns null. Make sure your console.log is in the render block. This is because set-state actions are asynchronous and are batched for performance gains. This is explained in documentation of setState.
Take a look at this => Why is setState in reactjs Async instead of Sync?
You need to bind the function context to current class/ component. Try something like below. Create a function like "callSetState" bind it with this and while calling in then just pass the values as parameters to it.
import React, { Component } from 'react';
class Test extends Component{
constructor() {
super()
this.callSetState = this.callSetState.bind(this);
}
callSetState(latitude, longitude) {
this.setState({
location_latitude: latitude,
location_longitude: longitude,
})
}
/* Call this func in your class and call callSetState which is already bound to your component in constructor */
// geocodeByAddress(location)
// .then(results => getLatLng(results[0]))
// .then(latLng => this.callSetState(latLng.lat, latLng.lng));
}
This is clearly an issue with your data call not react.
Your react code looks fine but I suggest spitting the code.
1) service.js file - this does your query and returns data or returns error.
2) location component, componentDidMount() or useEffect(() => {}, []) gets the data and updates state/useState()
—
To give you a code snippet I need to you to provide a console log of latLang & results.
Many thanks 👌🏻

Getting React errors when traversing an in-scope JSON object via Dot Notation [duplicate]

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

How to get value from array of objects?

I have an react app, and using redux and props to get array of objects into my component, and i am getting them. But i can't access particular property inside of one of objects that are in that array.
With this:
console.log(this.props.users)
I get listed array with all objects inside it. But when i need to access particular object or property of that object, for example:
console.log(this.props.users[0])
console.log(this.props.users[0].name)
I am getting error:
Cannot read property '0' of undefined
But when I iterate through array with map() method i have access to it, it works. Why can't i access it normally?
You are trying to access properties of this.props.users before it has loaded. Your component renders without waiting for your data to fetch. When you console.log(this.props.users) you say that you get an array, but above that, it probably logs undefined at least once when the component renders before this.props.users has loaded.
You have a couple of options. You can either do this at the very top of your render method to prevent the rest of the code in the method from executing:
if (!this.props.users) return null;
Once the data is fetched and props change, the render method will be called again.
The other option is to declare a default value, of an empty array for users in your reducer.
Might be when you are executing that line this.props.users is undefined. Check the flow where you have added console.log(this.props.users[0])
const App = () => {
const example = () => {
const data =[{id:1 ,name: "Users1", description: "desc1"},
{id:2 ,name: "Users2", description: "desc2"}];
return (
<div>
{data.map(function(cValue, idx){
console.log("currentValue.id:",cValue.id);
console.log("currentValue.name:",cValue.name);
console.log("currentValue.description:",cValue.description);
return (<li key={idx}>name = {cValue.name} description = {cValue.description}</li>)
})}
</div>
);
}
return(
<p style = {{color:'white'}}>
{example()}
</p>
);
}
export default App;

An array with objects, within an object is undefined

I have an array that contains objects, inside an object.
I can console.log the first object and the array, but when i try to access the objects within the array or use the map-function on the array i get an error that says "Can't read property of undefined".
I have thoroughly searched SO and other sites for similar problems and found some but no answers seems to work for me.
The object looks like this:
{
answers: [{…}],
createdAt: "2019-01-23T10:50:06.513Z",
nested: {kebab: "jjjj", sås: 2, sallad: "kkk"},
text: "weaxcc",
/* etc... */
}
And i can access it using: this.state.data
I want to access objects inside the answers-array like:
this.state.data.answers[0].text
or even :
this.state.data.answers.map().....
But that gives me 'Cannot read property '0' of undefined. The answers-array is not empty.
Any help is appreciated!
EDIT
This is how the objects ends up in my state.
getQuestionFromDb = () => {
axios.get(`http://localhost:3000/questions/${this.state.id}`)
.then(res => this.setState({
data: res.data
}));
};
This function is called in the ComponentDidMount()-method.
Here is my render function (the console.log is causing the error):
render() {
return (
<div className="main-content">
<h2>{this.state.data.text} </h2>
{console.log(this.state.data.answers[0].text)}
<p>Introducing <strong>{this.state.id}</strong>, a teacher who loves teaching courses about <strong>{this.state.id}</strong>!</p>
<input
type="text"
onChange={e => this.setState({ message: e.target.value })}>
</input>
<button onClick={() => {this.handleAnswerPost(this.state.message)}}>Answer</button>
</div>
);
}
}
componentDidMount is getting called when your component becomes part of the DOM but the call you do to populate your state is async due to XHR, which may take 100-300ms more to get the data in your component, so this.state.data.answers won't be available in the initial render() cycle.
since you mentioned using a loop, I suggest setting an initial state shape like
this.state = {
data: {
answers: []
}
}
your initial render won't have anything to loop but as soon as it resolves the data and sets the new state, it will render correctly.
alternatively you can
return this.state.data.answers.length ? loopItemsHere : <div>Loading..</div>
obviously, loopItemsHere can be anything you write to show the answers.
This might not be working when data doesnt contain answers[] at the very first mount for a component.
You may wanna check for your array's existance as following:
const { data } = this.state;
data.hasOwnProperty('answers') && console.log(data.answers[0]);
The reason we can't access object key in Javascript, usually it is because the variable/value is not the type of Object. Please ensure that this.state.data type is Object. You can try to console.log( typeof this.state.data ). We should expect that it is output object. If the type is string, you should parse it first with JSON.parse().
There may be a number of reasons that could cause the data object not to be shown:
The object was not in state at the time of it being called. This may be caused by an async operator not loading in the data. E.g. if you are requesting for the object from the database, chances are that at the time of making a call to retrieve the data (this.state.data), the response had not been given.
The object may not have been parsed into string. Try running console.log(typeof this.state.data). If the output is string, then you may have to parse it. If the output is undefined, then point one is valid

Parsing JSON objects array and displaying it in ReactJS

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: {}
}
}
}

Categories

Resources