React app: Undefined value after fetch - javascript

I have the following code where i fetch data from twitter API feed. I use a call back function and setState the values to my state properties. When I use it in render to just console and see the value then it shows
"Cannot read property 'created_at' of undefined".
I think it is trying to fetch before it is even available. I don't know what to do here. Can someone please help.
When i use console.log(this.state.twitterfeed.techcrunch[0]) I do not get any error.
I got the object
But when I use console.log(this.state.twitterfeed.techcrunch[0].created_at) then I got error
class Columns extends Component {
constructor() {
super();
this.state = {
twitterfeed: {
techcrunch: [],
laughingsquid: [],
appdirect: []
}
};
}
updateTwitterFeed = (data, user) => {
var twitterfeed = { ...this.state.twitterfeed };
if (user === "appdirect") {
twitterfeed.appdirect = data;
} else if (user === "laughingsquid") {
twitterfeed.laughingsquid = data;
} else {
twitterfeed.techcrunch = data;
}
this.setState({ twitterfeed });
};
componentDidMount() {
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=techcrunch"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "techcrunch"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "laughingsquid"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "appdirect"));
}
render() {
return (
<div className="container mx-0">
<div className="row">
<div className="col-4 col-md-4">
{console.log(this.state.twitterfeed.techcrunch[0].created_at)}
<Column tweet={this.state.twitterfeed.techcrunch} />
</div>
</div>
</div>
);
}
}

this.state.twitterfeed.techcrunch[0] will be undefined before your fetch is complete, so trying to access created_at on that will give rise to your error.
You could e.g. render null until the techcrunch array has been filled after the request.
class Columns extends Component {
// ...
render() {
const { techcrunch } = this.state.twitterfeed;
if (techcrunch.length === 0) {
return null;
}
return (
<div className="container mx-0">
<div className="row">
<div className="col-4 col-md-4">
<Column tweet={techcrunch} />
</div>
</div>
</div>
);
}
}

I have a couple of recommendations here.
First of all, try to consolidate your fetch logic using Promise.all.
Take a look at the documentation here. This will make
fetch(..).then(..)
fetch(..).then(..)
fetch(..).then(..)
into
Promise
.all([fetch(..), fetch(..), fetch(..)])
.then(...) // all 3 responses here
Also when rendering a React component componentDidMount() runs after render(). Take a look at react lifecycle here.
So the solution to be sure that the data you want to render is available is to have a flag on state such as:
this.state = { loading: True, ... } // constructor
Promise
.all([...])
.then(...)
.then(args =>
this.setState({loading: False});
...) // componentDidMount()
render() {
if(this.state.loading)
return <div>Loading...</div>
return (
// your component with data already set into the state :)
)
}

Related

React js fetch API with componentDidMount()

I am learning React.js and trying to fetch API with fetch() and I tried to use componentDidMount() but I have a problem, you can see the pic at the end of the post.
import React, { Component } from 'react'
export default class App extends Component {
state = {
weather: []
};
fetchData() {
fetch('prevision-meteo.ch/services/json/rochelle-17')
.then((response) => response.json())
.then((obj) => {
console.log('javascript object: ', obj)
this.setState({ weather: obj.results});
})
.catch((error) => {
console.error(error)
})
}
componentDidMount() {
console.log('Le composant App est monté sur le DOM !')
this.fetchData();
}
render() {
return (
<div>
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}>{weatherRecord.city_info.name}</div>
))}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me</button>
</div>
)
}
}
I want to get the name of city_info in my page but didn't work!
This is the results in the console, can anyone help me?
Setting state is asynchronous so React is rendering before the state has been set. What you can do is put in a short circuit condition this.state.weather && to check if the weather array exists and only when it is available will the map operation be performed and you shouldn't get any errors.
import React, { Component } from 'react'
export default class App extends Component {
state = {
weather: []
};
fetchData() {
fetch('http://localhost:3000/rochelle-17.json')
.then((response) => response.json())
.then((obj) => {
//console.log('javascript object: ', obj)
this.setState({ weather: obj.results});
})
}
componentDidMount() {
console.log('Le composant App est monté sur le DOM !')
this.fetchData();
}
render() {
return (
<div>
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}></div>
))}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me</button>
</div>
)
}
}
Some Notes:
The newer versions of React support setting initial state like this, which is a bit cleaner:
state = {
weather: []
}
It's also good practice to catch errors in case the API call fails. You can simply use .catch like this after the last .then():
.catch((error) => {
console.error(error)
})
Since ES6 you don't need to use return for the <div> you are rendering. You can simply use map with curved brackets () instead of curly brackets {} to implicitly return
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}></div>
))}
Try this
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
wheather: null
};
}
fetchData() {
fetch("http://localhost:3000/rochelle-17.json")
.then((response) => {
return response.json();
})
.then((obj) => {
//console.log('javascript object: ', obj)
this.setState({ wheather: obj });
});
}
componentDidMount() {
console.log("Le composant App est monté sur le DOM !");
this.fetchData();
}
render() {
return (
<div>
{this.state.wheather
&& <div key={this.state.wheather.city_info.name}>{this.state.wheather.city_info.name}</div>
}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me !</button>
</div>
)
}
}

TypeError: projects.map is not a function (React)

Components
const Pcards = ({ projects }) => {
return (
<div>
<CardColumns>
{projects.map((projects) => (
<Card>
<Card.Img variant="top" src={"http://localhost:8000" + projects.images[0].file_path + projects.images[0].file_name + projects.images[0].file_type} />
Pages
class Projects extends Component {
state = {
projects:[]
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
this.setState({ projects: data })
})
.catch(console.log)
}
render () {
return (
<Pcards projects = {this.state.projects} />
);
}
}
New to react and this code returns
TypeError: projects.map is not a function
This appears to be compiling just fine on my partner's end since he written this code and I'm trying to expand on his work.
I've seen other similar posts but unable to find a fix. Any idea what's going on here?
You have two mistakes in your Projects class.
1- .catch error handling syntax was wrong
2- you were not checking the fetched data
class Projects extends Component {
state = {
projects: []
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
if (data && data.length) { // checking the data
this.setState({ projects: data })
} else {
console.log("Projects fetch failed, check your api code!")
}
})
.catch(e => console.log(e)); // corrected error catch
}
render() {
return (
<Pcards projects={this.state.projects} />
);
}
}
You can also edit your Pcards component code. You are already using a property called projects and you are mapping it, calling the argument projects too. That is not a good practice. If you are mapping projects name the item as project or projectItem.
projects.map((project) => ...
Try
class Projects extends Component {
constructor(props){
super(props)
this.state = {
projects:[]
}
}

TypeError: this.state.posts.map is not a function when setting state to a array

I get the following error when mapping the state for my posts:[] object
TypeError: this.state.posts.map is not a function
I looked at something similar but it didn't come close to resolving the problem.
The console.log() shows the current data, so the function works, it just a matter of passing the data within the posts:[] object and mapping it.
{this.state.posts.map((post, key)=> {
return(
<div key={key}>
<h2>{post.description}</h2>
<small>{post.username}</small>
</div>
);
})}
Dashboard.js (updated)
class Dashboard extends Component{
constructor(props){
super(props)
this.state = {
username:"",
loading: true,
posts:{},
myArray:[]
}
}
componentWillMount(){
this.getPosts()
}
getPosts() {
const collection2 = fire.collection('posts');
collection2.get().then(snapshot => {
snapshot.forEach(doc => {
let items = doc.data();
// items = JSON.stringify(items);
this.setState({
posts: items
});
})
const eArray = Object.values(this.state.posts);
this.setState({
myArray:[...eArray]
})
console.log(this.state.myArray)
})
}
componentDidMount(){
if(this.props.userId){
const collection = fire.collection('users');
collection.get().then(snapshot => {
snapshot.forEach(doc => {
this.setState({
username: doc.data().username,
loading:false
})
});
});
}
}
render(){
if (!this.props.userId) return <Redirect to='/' />
const { loading, posts,myArray } = this.state;
if(loading){
return(
<div className="loader"></div>
)
}
return(
<div className="container">
<div className="row">
<div className="col-md-6 mt-3">
<h1>Welcome {this.state.username.toLowerCase()}</h1>
{myArray.map((post, key)=> {
return(
<div key={key}>
<h2>{post.description}</h2>
<small>{post.username}</small>
</div>
);
})}
</div>
</div>
</div>
);
}
}
The error that you're getting in the console is because you're using .map on an Object, but map is a method of Array. More info on .map
There's also another problem with your code and is that you shouldn't call this.setState inside a loop. Instead, collect the data you need from the loop and then update the state.
Something like this
getPosts() {
const collection2 = fire.collection('posts');
collection2.get().then(snapshot => {
let arr = [];
snapshot.forEach(doc => {
arr.push(doc.data()); //Collect all the items and push it into the array
})
this.setState({
posts: arr,
})
})
}
I don't know exactly what you're trying to do on componentDidMount. But this should point you in the right direction and solve the error that you're experiencing with map not being a function.
You setting state in forEach loop. You just set last element of collection2.get() result to state.posts which makes forEach loop meaningless. There is no difference
collection2.get().then(snapshot => {
snapshot.forEach(doc => {
this.setState({
posts: doc.data()
})
})
console.log(this.state.posts)
})
with
collection2.get().then(snapshot => {
this.setState({
posts: snapshot[snapshot.length - 1].doc.data()
})
})
setState asynchronous and will conflict with the different states available on each iteration in that forEach () loop.

React doesn't re-render the Virtual DOM after the state is changed

I have a component that loads courses from RESTful API. After the courses have been loaded state is changed and the component should render CourseCard components with the loaded data.
The problem is that when loadCourses() method changes the state of the component no CourseCard is displayed
This is code of the component:
class Courses extends Component {
constructor() {
super();
this.state = {
courses: []
};
}
componentDidMount() {
this.loadCourses(null);
}
render() {
return (
<div>
<CourseCategoriesBar loadCourses={this.loadCourses.bind(this)} />
<div style={{
paddingBottom: "120px",
marginBottom: "30px",
}}>
<div className="container">
<div className="row">
{this.state.courses.map((course, i) => {
return (
<div className="col-lg-3 col-md-6 col-sm-12" key={i}>
<CourseCard type="e" key={i}/>
</div>
);
})}
</div>
</div>
</div>
</div>
);
}
loadCourses(id) {
if (!id) id = ""
let url = "http://localhost:8000/api/courses/category/" + id;
fetch(url)
.then((response) => response.json())
.then((response) => this.setState(response));
}
}
I believe you are not updating state, update the state like this
fetch(url)
.then((response) => response.json())
.then((response) => this.setState({courses : response.data});
My guess would be that your fetch response is an array, not an object.
Which means you will have a state object with properties 0,1,2 etc (like any array) instead of 'courses'.
try to map your response to the courses attribute:
.then(response => this.setState({courses: response}))

componentDidUpdate() working mostly, but not rendering new data

I am using the componentDidUpdate() method and for the most part, it is doing what it should. It runs the function to get the data from the API as well as logs it to the console. The problem is that it does not render the new data on the front end. The only time it renders the new data is if the component actually mounts (if the page is refreshed). I feel like I'm very close, but have hit a dead end. Here is my code:
import React from 'react';
import Nav from './Nav';
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
APIData: []
}
}
getAPIData() {
const url = `http://localhost:3001${this.props.location.pathname}`;
return fetch(url, {
method: 'GET',
mode: 'CORS',
headers: {
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
console.log(data);
return data;
}).catch(err => { console.log('Error: ', err) });
};
dataList() {
return (
<div>
{this.state.APIData.map((APIData) => (
<p> And the data returned is -> {APIData.firstName}
{APIData.lastName} !</p>
)
)}
</div>
)
}
componentDidMount() {
console.log(this.props.location.pathname);
this.getAPIData()
.then(data => {
console.log('in List.js ', data);
this.setState({
APIData: data
});
});
}
componentDidUpdate(prevProps, prevState) {
console.log(this.props.location.pathname);
// only update if the data has changed
this.getAPIData()
.then(data => {
if (prevProps.data !== this.props.data) {
this.setState({
APIData: data
});
}
console.log(data);
});
}
render() {
return (
<div>
<Nav />
<br />
<br />
<br />
<br />
<div>
{/* {this.state.APIData.map((APIData) => (
<p> And the data returned is -> {APIData.firstName}
{APIData.lastName} !</p>
)
)} */}
{this.dataList()}
</div>
</div>
);
}
}
export default List;
I think it may be this block:
if (prevProps.data !== this.props.data) {
this.setState({
APIData: data
});
}
Are you actually passing a data prop to this component?
If not, then it would be checking if undefined !== undefined and never executing.
If you are, then you might check if the data reference is actually changing, or you're just mutating the inside of the object.

Categories