ReactJS: this.state is undefined on lifecycle hook [duplicate] - reactjs

This question already has answers here:
cannot get the parent property this property when I have two inner loop
(2 answers)
Closed 5 years ago.
I'm building a simple electron-react component.
This component queries enabled Network Interfaces, pushes the data to the state and renders it to the DOM. The component doesn't work because of this error:
"Uncaught TypeError: Cannot read property 'state' of undefined" at this line:
"data = this.state.data.slice();"
Why is this.state undefined? did I miss binding something? Thanks for any advice.
import React from "react";
import os from "os";
export default class GetInterfaces extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
};
componentDidMount() {
let ifaces = os.networkInterfaces();
Object.keys(ifaces).forEach(function (ifname) {
let data;
ifaces[ifname].forEach(function (iface) {
if (iface.internal === true || iface.family === "IPv6") {
return;
}
let networkInterface = {
name: ifname,
mac: iface.mac,
ip: iface.address
};
data = this.state.data.slice();
data.push(networkInterface);
this.setState({ data: data });
});
});
}
render() {
const networkInterfaces = this.state.data.map((networkInterface, index) =>
<ListGroupItem key={index}>
<i class="fa fa-wifi"></i>
<span>{networkInterface.name}</span>
<input type="checkbox"></input>
</ListGroupItem>
);
return (
<div>
{networkInterfaces}
</div>
);
}
}

I think "this" is becoming unbound in the .forEach function, so you can try the trick of saving the context before it like this
componentDidMount() {
let ifaces = os.networkInterfaces();
let that = this; // Note the saving of the context
Object.keys(ifaces).forEach(function (ifname) {
let data;
ifaces[ifname].forEach(function (iface) {
if (iface.internal === true || iface.family === "IPv6") {
return;
}
let networkInterface = {
name: ifname,
mac: iface.mac,
ip: iface.address
};
data = this.state.data.slice();
data.push(networkInterface);
that.setState({ data: data }); // And then using the saved context
});
});
}

this happens because you are losing your this context inside forEach call
as docs says you can pass additional parameter to forEach, which is thisArg:
componentDidMount() {
let ifaces = os.networkInterfaces();
Object.keys(ifaces).forEach(function (ifname) {
...
}, this);
}

Related

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

React - Render happening before data is returned and not updating component

I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}

React componentDidMount and setState not seeming to cause a rerender to show my component [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
/*After fetching data and setting to state, I am attempting to generate an array of jsx items to display. But the array is showing as empty and nothing is rendering.
Tried, hardcoding and this works. Tried loggin the data and it does show up that state is receving the data.
Removed my authorization token from the code below.
*/
import React, { Component } from 'react';
import AddCard from '../AddCard/AddCard.js';
import KanCard from '../KanCard/KanCard.js';
import './CardHolder.scss';
export default class CardHolder extends Component {
constructor(props){
super(props);
this.state = {
stories: [],
inProgressTasks: [],
completeTasks: [],
};
}
componentDidMount(){
// let id = this.props.id;
let id = 168881069;
let completetask = [];
let progresstask =[];
var data = null;
var xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
let parsedResponse = JSON.parse(this.responseText);
for( let taskResponse of parsedResponse ){
let task = {
key:taskResponse.id,
id:taskResponse.id,
story_id:taskResponse.story_id,
complete:taskResponse.complete,
description: taskResponse.description,
}
if(!taskResponse.complete){
progresstask.push(task)
} else {
completetask.push(task);
}
}
}
});
this.setState({inProgressTasks:progresstask, completeTasks:completetask})
xhr.open("GET", `https://www.pivotaltracker.com/services/v5/projects/2401708/stories/${id}/tasks`);
xhr.setRequestHeader("X-TrackerToken", "296912a3ff4ddcda26b4a419934b3051");
xhr.setRequestHeader("Accept", "*/*");
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("cache-control", "no-cache");
xhr.send(data);
}
render(){
let completeTasks = this.state.completeTasks.map((task)=>{
return (
<KanCard
key = {task.id}
id = {task.id}
story_id = {task.story_id}
complete = {task.complete}
description = {task.description}
/>
)
})
let inProgressTasks = this.state.inProgressTasks.map((task)=>{
return (
<KanCard
key = {task.id}
id = {task.id}
story_id = {task.story_id}
complete = {task.complete}
description = {task.description}
/>
)
})
console.log(inProgressTasks)
return (
<div className='holder'>
<h2> {this.props.title} </h2>
<div>
<h3>In Progress</h3>
{inProgressTasks}
</div>
<div>
<h3>Complete</h3>
{completeTasks}
</div>
<AddCard />
</div>
)
}
}
There are a few issues with the way you're setting your call up and updating your state.
First, make sure you update your state when you get your response back, after all, it's an asynchronous request and you need to wait to get something, then update your state.
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
let parsedResponse = JSON.parse(this.responseText);
for( let taskResponse of parsedResponse ){
let task = {
key:taskResponse.id,
id:taskResponse.id,
story_id:taskResponse.story_id,
complete:taskResponse.complete,
description: taskResponse.description,
}
if(!taskResponse.complete){
progresstask.push(task)
} else {
completetask.push(task);
}
}
this.setState({inProgressTasks:progresstask, completeTasks:completetask})
}
});
Second, remember you're inside a class, so this.readyState and this.responseText are referencing the class when you use the keyword this, not your XHR object as you're expecting it to. In order to make this work, you should change the readystatechange's callback function to a lambda function, then replace the this for xhr, yet you should keep the this that actually makes a reference to your class in the this.setState:
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4) {
let parsedResponse = JSON.parse(xhr.responseText);
for( let taskResponse of parsedResponse ){
let task = {
key:taskResponse.id,
id:taskResponse.id,
story_id:taskResponse.story_id,
complete:taskResponse.complete,
description: taskResponse.description,
}
if(!taskResponse.complete){
progresstask.push(task)
} else {
completetask.push(task);
}
}
this.setState({inProgressTasks:progresstask, completeTasks:completetask})
}
});
I tried to replicate your issue here:
I'm hitting a dumb api and updating my state with the response data. Play around with it. Change the readystatechange's callback function from being a lambda function to being an anonymous function as you initially set up and see what happens.
To read more on the this problem, take a look at this question.

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

React: Google Places API/ Places Details

I have the following code which retrieves Google Places Reviews based on Google Places API. I have incorporated the logic to work as a React life cycle component. Currently, I am unable to setState and correctly bind the object. I could use some help understanding where my logic is failing.
export default class Reviews extends React.Component{
constructor(props){
super(props);
this.state = {
places: []
}
}
componentDidMount(){
let map = new google.maps.Map(document.getElementById("map"), {
center: {lat:40.7575285, lng: -73.9884469}
});
let service = new google.maps.places.PlacesService(map);
service.getDetails({
placeId: 'ChIJAUKRDWz2wokRxngAavG2TD8'
}, function(place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
console.log(place.reviews);
// Intended behavior is to set this.setState({places.place.reviews})
}
})
}
render(){
const { places } = this.state;
return(
<div>
<p>
{
places.map((place) => {
return <p>{place.author_name}{place.rating}{place.text}</p>
})
}
</p>
</div>
)
}
}
You can't use this that way in a callback. When the function is called the this in, this.setState({places.place.reviews}) doesn't point to your object. One solution is to use => function notation which will bind this lexically.
service.getDetails({
placeId: 'ChIJAUKRDWz2wokRxngAavG2TD8'
}, (place, status) => {
if (status === google.maps.places.PlacesServiceStatus.OK) {
console.log(place.reviews);
this.setState({places: place.reviews})
}
})
}
Alternatively you can make a new reference to this and us it in the function. Something like
var that = this
...
that({places.place.reviews})
The first option is nicer, but requires an environment where you can use ES6. Since your using let you probably are okay.
With some tweaking -- I got the code to work! Thank you.
render(){
const { places } = this.state;
return(
<div>
<p>
{
places.map((place) => {
if(place.rating >= 4){
return <p key={place.author_name}>{place.author_name}{place.rating}{place.text}</p>
}
})
}
</p>
</div>
)
}

Resources