Cannot read property 'destination' of undefined when react state is an array - arrays

when my react state is an object, I can get its property in render method,
but when I set the state to array and use state[0].property in render method, it give me the undefined error, cannot figure out, any help??? Thanks!!!
class App extends Component {
state={
dataArray:[]
}
componentDidMount(){
this.getTrainInfo();
}
getTrainInfo=()=>{
fetch('https://api-v3.mbta.com/routes')
.then(response=>response.json())
.then(res=>{
//res will return an array of objects
let arr=[];
let data={};
data.destination=res.data[0].attributes.direction_destinations[0];
data.lineID=res.data[0].relationships.line.data.id
arr.push(data);
this.setState({dataArray:arr});
//I put a console.log(dataArray) here, I can get
// [{destination:xxx, lineID:xxx }] back.
})
}
render() {
//in here, I also can get the array: [{destination: xxx, lineID: xxx}]
let data=this.state.dataArray;
//but in here, I get error saying property destination is undefined,
//why?
let destination=data[0].destination;
//console.log(destination);
return (
<div className="App">
<h1>Train info</h1>
</div>
);
}
}

This is normal behavior. React will render you component a single time before ever executing the logic in componentDidMount() Which is why you're getting the undefined error because your inital state starts out as an empty array.
To resolve this, it is common practice to have a "loading state" while your component updates with the new data. So when the state is finally updated, your component will re-render and display your desired content.
In your render, try doing something like:
render() {
let data=this.state.dataArray;
if(this.state.dataArray.length == 0){
return Loading...
} else {
return (
<div className="App">
// whatever logic you want to display
<h1>Train info</h1>
</div>
)
}

You need to add condition because on initial render the dataArray is empty array and doesn’t have any objects in it.
From second render on wards you have data in dataArray so add below condition
if(this.state.dataArray.length){ //this condition will be true when dataArray length is greater than zero
let destination=data[0].destination;
console.log(destination); //this will print the value
}

Related

Accessing State that holds Array of Objects

I apologize if this is a basic question, but I'm genuinely confused why this isn't working. I have a component that makes an API call to fetch data, and the data returns successfully, and I can do a console.log and see an array of objects as I expect.
.then((result) => {
this.setState({
surveyData: result,
surveyYear: result[0].year
});
console.log(result); <--- This logs an array of objects as expected
console.log(this.state.surveyData); <-- This logs an empty array
console.log(this.state.surveyYear); <-- This logs what I expect
})
When I use the return component, I get what I expect from this:
render(){
return(
<p>{this.state.surveyYear}</p> <--- this shows exactly what I'd expect
)
}
But if I do the below, it should show the exact same data, but instead, I get this error: TypeError: Cannot read properties of undefined (reading '0') Is it possible to do this way?
render(){
return(
<p>{this.state.surveyData[0].year</p> <--- I want to access the data this way instead
)
}
This is a common pitfall in react. The issue is that updating the state is not instantaneous. This is why your data is not there yet right after the call to setState. Similarly in the render method you will need to guard against that data not being available yet.
If you babel your code and support the optional chaining operator:
render(){
return(<p>{this.state.surveyData?.[0]?.year</p>)
}
otherwise
render(){
return(<p>{this.state.surveyData && this.state.surveyData[0] && this.state.surveyData[0].year</p>)
}

React can not access variable from return

I am very new to React and I can not solve this issue for a very long time. The idea is the following, I get some user info from database and pass it into cookies. From react side I get this cookies as JSON object and I need to render menu for user depending on values in this object. Right now I pass this object that I get from cookies from parent Component and I try to access it from Settings component. The issue is that I can see access this object from test function, however when I try to access this data from return it gives me undefined error. The function that returns value from cookies is sync, I have no idea why that might happen, please help.....
Since this.state.shopSettings["new_orders"] is boolean, use ternary.
Don't copy props to the state inside constructor as the constructor is executed only once. So updates to the props won't be reflected in the component.
Like this
<button onClick={this.test}>
{props.shopSettings && (props.shopSettings["new_orders"] ? 'true button' : 'false button')}
</button>
It solves very easily with this code, now I can access any key from shopSettings inside return without any issues
class Index extends React.Component {
state = {
shopSettings: Cookies.getJSON('shopSettings')
}
render() {
const {shopSettings} = this.state
if (!shopSettings) {
return null
}
return (
<div>Hello</div>
)
}

React - wait for div to load fully

So Im fetching some messages from DB and then I'd like to show them in a chat div. Here's the code:
componentDidMount():
socketio.on("message", function(data) {
// socketio is initialized outside of class
// and the server emits "message" on client connection
that.setState({
// before this happens, there is no "messages" var in state
messages: data
});
});
render():
<div class="chatbox">
{messages &&
messages !== undefined &&
messages.length > 0 ? (
messages.map(function(item, i) {
if (item.from === uid) return <div class="message-incoming">{item.body}</div>;
else return <div class="message-outgoing">{item.body}</div>;
})) : (
<></>
)
}
</div>
The message get loaded into a div with class "chatbox". What I would like to do is to wait for these messages to get rendered and after that manually scroll the "chatbox" to bottom, so users can see latest messages. But so far I haven't figured out a way to make this work, because no matter what code I try, it gets triggered before map finishes and not after. Basically I would like to do:
document.getElementById("chatbox").scrollTop =
document.getElementById("chatbox").scrollHeight;
But no matter where i execute this code, scrollHeight is always the same as initial height of "chatbox", because the line gets triggered before all messages are rendered. I want to trigger it after the div is completely loaded.
Any ideas?
Just return null if messages is not yet populated:
render() {
if(!this.state.messages) return null;
<div class="chatbox">
...
I recommend you use the componentDidUpdate lifecycle method to scroll to the bottom.
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. [...]
class ChatComponent extends Component {
componentDidUpdate(prevProps, prevState) {
if (prevState.messages.length != this.state.messages.length) { // Or any other comparison
document.getElementById("chatbox").scrollTop = document.getElementById("chatbox").scrollHeight;
}
}
/* ... */
}

data is not coming outside the function in reactjs

How to bring the pushed array data outside the function,Every thing is
working fine in click function.But when Im trying to print this outside of
the function console.log(this.state.selectedSchedule[0].storeName) , It is
throwing the error:
Cannot read property 'storeName' of undefined.
Code:
this.state = {
selectedSchedule:[]
};
click(e){
console.log('e',e);
document.getElementById("detailView").style.display="block";
this.state.selectedSchedule=[];
for(let i=0;i<this.scheduless.length;i++){
if(this.scheduless[i].scheduleId === e){
this.state.selectedSchedule.push(this.scheduless[i]);
break;
}
}
console.log('cheudkl',this.state.selectedSchedule);
this.setState=({
selectedSchedule:this.state.selectedSchedule
});
console.log(this.state.selectedSchedule[0]);
console.log(this.state.selectedSchedule[0].storeName) //printing
};
//html render
render(){
console.log(this.state.selectedSchedule[0].storeName) // here it's throwing me the error(not printing)
}
Reason is, initial value of this.state.selectedSchedule is [] and during initial rendering you are trying to get the storeName of 0th items, and clear at that time 0th time doesn't exist so
this.state.selectedSchedule[0] ===> undefined
And you are trying to get the value from undefined that's why it is throwing the error. Simply put the check on length before printing the values.
Like this:
render(){
// now it will print the values once some item will be added
if(this.state.selectedSchedule.length)
console.log(this.state.selectedSchedule[0].storeName);
return (....)
}
Another issue is in the way you are updating the state value.
Changes:
1- Never mutate the state value directly, Treat this.state as if it were immutable.
2- setState is a function so we need to call it this.setState({}).
write it like this:
click(e){
document.getElementById("detailView").style.display="block";
let arr = [];
arr = this.scheduless.filter(el => el.scheduleId === e);
this.setState({
selectedSchedule: arr
});
};
You should not mutate the state, push is mutating the array, and you are re assigning the state object in the click function this.state = ....
Instead, use the builtin setState method of react to update it:
this.setState({key: value});
As for arrays you can add items with the ES6 spread operator without mutating it:
const nextState = [...this.state.selectedSchedule, newValue];
this.setState({selectedSchedule: nextState });
EDIT
As Mayank Shukla mentioned, in addition to this solution you should check to see if the length of your array is bigger then 0:
this.state.selectedSchedule.length > 0 && console.log(this.state.selectedSchedule[0].storeName);
In the first render call this array is empty hence its length is 0.
This is not correct way of updating the state
this.setState=({
selectedSchedule:this.state.selectedSchedule
});
You need to edit this as :
if(this.scheduless[i].scheduleId === e){
this.setState({selectedSchedule:[...this.state.selectedSchedule, this.scheduless[i]]});
break;
}
You need to set the state only then the render function will be called again and your changes to the state variable will be visible.
You should never mutate the state (changing the contents of this.state) directly.
That is, instead of assigning an empty array to part of the state and then push every item, create a new array:
const selectedSchedule = this.scheduless.filter(schedule => schedule.scheduleId === e)
this.setState({selectedSchedule});

React + Firebase - Deleting firebase object inside mapped components

I'm trying to pass the key of my project object through to my mapped components in order to delete them.
UPDATE: using the _.forEach(project, (proj, key) => <Project key={key} {... more props here}/>) Throws an error about objects not being valid react children. I'm guessing this is because the project i'm forEaching over needs to be turned into an array? I keep trying to format in componentWillMount() but when i try to run forEach with setState and push to a new array i keep getting duplicates
componentDidMount() {
projectRef.on('value', snap => {
this.setState({projects: snap.val()})
// somehow i need to create a new array of objects that include the key.
})
}
UPDATE: i removed the codepen example. I like code sandbox better. Much better. =)
And here's the code sandbox (If you get an error about the [DEFAULT] app already being defined just refresh the output browser and it will work. I don't know why it's doing that... oh well. I added my attempt with forEach on the code sandbox example. Hopefully someone can let me know what i'm doing wrong.
Yep, map returns an array of the values of the object, in this case an object you can then access via the props in the <Display /> component, but not the key of each element of the object.
Perhaps you could use lodash's forEach in order to loop and have access to both the key and the value of each element in your data collection. Like that you can pass the key (that will be the target for the remove method) as a specific prop and the value as the item prop in the component.
export default class extends React.Component {
constructor() {
super()
this.state = {
items: null
}
}
render() {
return() {
<div>
{_.forEach(this.state.items, (item, key) =>
<Display key={key} itemKey={key} item={item}/>
)}
</div>
}
}
}
// then the display component
removeItem() {
firebase.database().ref({this.props.itemKey}).remove();
}
render() {
return (
<div>{this.props.item.name} <button onClick={this.removeItem}>X</button>
)
}
Here's a simple live example of how forEach works:
https://jsbin.com/xolejoj/edit?js,console
Edit 08-12-2017
The problem with your code is that you're missing the fact that map is returning the key and the value of each element of the object. In this case your object has a key string and a value that is the object. Then on your JSX you're trying to pass the key as it were a part of the object (value) but is not, therefore you're getting an undefined value in the component's props.
Change your code to this:
<div>
{_.map(projects, (proj, key) => <Project
key={key}
title={proj.title}
subtitle={proj.subtitle}
desc={proj.desc}
itemKey={key} // just the key of the object
/>
)}
</div>
The thing is that the key of each object is the identifier in firebase and the value is the object with the data you need, but that object doesn't have a key property, therefore it was evaluated to null.

Resources