I'm super new to React and this might be a dumb question but I'm getting an error saying: Uncaught TypeError: Cannot read property 'map' of undefined
when I'm trying to display the ingredients for each recipe. I'm not sure if I can use an array.map inside of the return statement, or where else should I include it, if it is a part of my component? I just want to go through the ingredients and display them . Thanks for any help]1
import React, { Component } from "react";
class Recipe extends Component {
state = {
activeRecipe: []
}
componentDidMount = async() => {
const title = this.props.location.state.recipe;
const req = await fetch
(`https://api.edamam.com/search?q=${title}&app_id=${API_ID}&app_key=${API_KEY}`);
const res = await req.json();
this.setState({ activeRecipe: res.hits[0].recipe});
console.log(this.state.activeRecipe);
}
render() {
const recipe = this.state.activeRecipe;
return (
<div className="container">
<div className="active-recipe">
<h3 className="active-recipe">{recipe.label}</h3>
<div className="container">
{recipe.ingredients.map(ingredient => {
return (
<p>{ingredient.text}</p>
);
})}
</div>
</div>
</div>
);
}
}
export default Recipe;
This is because your component is rendered/mounted before the async call can finish..
If you change:
recipe.ingredients.map(ingredient => {
to this:
recipe.ingredients && recipe.ingredients.map(ingredient => {
It should work as you are wanting.
This is because map does not get called unless ingredients exist.
Related
This is the data that I fetched from firestore and I want to use it in my application to show the product details but I can't retrieve it into my application. Example, I want the data from shop->collections->bags->items to show the specific product details.
And here is my code in product-details.component.jsx:
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { fetchCollectionsStart } from '../../redux/shop/shop.actions'
const ProductDetailsPage = ({ fetchCollectionsStart, match, collections }) => {
const {title, items} = collections;
useEffect(() => {
fetchCollectionsStart();
}, [fetchCollectionsStart]);
console.log(match);
const ShowProduct = () => {
return (
<div>
{title}
</div>
)
}
return (
<div className='product-details'>
<div className="row">
<ShowProduct />
</div>
</div>
);
};
const mapDispatchToProps = dispatch => ({
fetchCollectionsStart: () => dispatch(fetchCollectionsStart())
});
export default connect(null, mapDispatchToProps)(ProductDetailsPage);
The error pop up is shown as the image below:
I don't know why I can't destructure the data from collections. I thought I already successfully fetched it into my application?
Thanks a lot for helping me!!
You need to destructure bags and then get the title from bags.
const { bags } = collections;
console.log(bags.title)
If you dont know whats in collections - you can use Object.keys() to get the different object keys- bags, boys, girls, hats etc.
I am trying to load pictures from jsonPlaceholder. When I console.log out the images array, it works successfully. The moment I try to call the URL from the element, it throws a TypeError. I tried to do it the asynchronous way, even this doesn't work. Is there any workaround for this? What possibly is the problem?
import React, {Component} from 'react';
import axios from 'axios';
class Home extends Component{
state = {
posts : [],
images : []
};
componentDidMount = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
this.setState({posts: res.data.slice(0,10)})
const res2 = await axios.get('https://jsonplaceholder.typicode.com/photos');
this.setState({images: res2.data.slice(0,10)})
}
render = () => {
const {posts} = this.state.posts;
const {images} = this.state.images;
const PostList = this.state.posts.length ? (
this.state.posts.map((post,index) => {
return(
<div className = "post card" key = {post.id}>
<div className = "card-content">
<div className = "card-title">
{post.title}
</div>
<div className>
{(this.state.images[index].url)} //console.log(this.state.images[index] works, but .URL doesn't
</div>
<div>
<p>{post.body}</p>
</div>
</div>
</div>
)
})) : (<div className = "center">No Posts!</div> )
return(
<div className = "container">
<div className = "center">
{PostList}
</div>
</div>)
}
}
export default Home;
The issue was in multiple setState. Once you setState, it updates and renders asynchronously. So didn't wait for next setState. Better set both data in one call.
componentDidMount = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
this.setState({posts: res.data.slice(0,10)})
const res2 = await axios.get('https://jsonplaceholder.typicode.com/photos');
this.setState({images: res2.data.slice(0,10)})
}
You can clean up code like this:
Working post: https://codesandbox.io/s/articlepodcast-wd2rv?file=/home.js:0-1214
import React, { Component } from "react";
import axios from "axios";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
images: []
};
}
componentDidMount = async () => {
const res = await axios.get("https://jsonplaceholder.typicode.com/posts");
const res2 = await axios.get("https://jsonplaceholder.typicode.com/photos");
this.setState({
posts: res.data.slice(0, 10),
images: res2.data.slice(0, 10)
});
};
render = () => {
const { posts, images } = this.state;
const PostList = posts.length ? (
posts.map((post, index) => {
return (
<div className="post card" key={post.id}>
<div className="card-content">
<div className="card-title">{post.title}</div>
<div className>{images[index].url} </div>
<div>
<p>{post.body}</p>
</div>
</div>
</div>
);
})
) : (
<div className="center">No Posts!</div>
);
return (
<div className="container">
<div className="center">{PostList}</div>
</div>
);
};
}
export default Home;
Because of your first setState, it goes to render before second async-await.
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
const res2 = await axios.get('https://jsonplaceholder.typicode.com/photos');
this.setState({images: res2.data.slice(0,10), posts: res.data.slice(0,10)})
try like this.
better to have something like this;
this.setState({
posts: res.data.slice(0,10).map((post, index) => ({
...post,
image: res2.data.slice(0,10)[index]
}))
})
The Problem
The error happens because of the way the state is set in componentDidMount function. To break down why this happens I will explain what the code is trying to do:
First, Axios will send a get request to obtain posts, because this is an async action that is being awaited javascript will work on something else while the data is being obtained. So it will continue and run the render function the first time. The render function will return No Posts! as this.state.posts.length ? is false.
Then when we finish getting the posts the componentDidMount will continue, in this case it will update the state this.setState({posts: res.data.slice(0,10)}), every time setState is called it will tell react to run the render function for that component again to make sure the page reflects what is in our state. Then we get to the next line where we await to get the photos, while we are waiting render function will run a second-time because (as just said) we changed the sate of the component. At this point in time, we still don't have the photos as we are currently awaiting for them but, we do have posts, so the check to see if we have the posts this.state.posts.length ? will be true. Then when we get to images[index] it will be undefined because we have not finished obtaining the photos and setting them into the state yet. (I hope this helps you understand the problem).
The Solution
Update the state after we have obtained all data so that the second time the render function runs we have images:
const res = await axios.get("https://jsonplaceholder.typicode.com/posts");
const res2 = await axios.get("https://jsonplaceholder.typicode.com/photos");
this.setState({
posts: res.data.slice(0, 10),
images: res2.data.slice(0, 10)
});
I would also suggest adding a safety check to this.state.images like you did with posts to make sure you actually have the data before you try to use it. This is good practice to do for all data that comes from network calls because many unexpected things could happen when trying to obtain data from a service.
Lastly, I also suggest you try following through your code with the chrome debugger. This will help you follow what the code is doing to get a better understanding of why an issue like this would be happening. Here is a nice guide on how to do that.
https://developers.google.com/web/tools/chrome-devtools/javascript
React Newbie
I am coding in React. I am taking an object of JSON data from a GET request to an api, and trying to pass it as a prop in a component. Then I am mapping over it to make a list of "trail" objects.
I am getting this error in the console:
"Warning: Failed prop type: Invalid prop trail of type array supplied to TrailItem, expected object."
Here's the code for my app level component:
import React, { Component } from "react";
import "./App.css";
import Navbar from "./components/layout/Navbar";
import Trails from "./components/trails/Trails";
import axios from "axios";
class App extends Component {
state = {
trails: {},
};
async componentDidMount() {
const res = await axios.get(
`https://www.hikingproject.com/data/get-trails?lat=35.0844&lon=-106.6504&maxDistance=10&key=${process.env.REACT_APP_HIKING_PROJECT_KEY}`
);
console.log(res.data);
this.setState({ trails: res.data });
}
render() {
return (
<div className='App'>
<Navbar />
<div>
<Trails trails={this.state.trails} />
</div>
</div>
);
}
}
export default App;
As far as I can tell, there is no problem with the data. A console.log(res.data); returns an object, so I know the api request is working.
Here's the code for my "Trails" component:
import React, { Component } from "react";
import TrailItem from "./TrailItem";
class Trails extends Component {
render() {
return (
<div style={trailStyle}>
{Object.keys(this.props.trails).map((key) => (
<TrailItem key={key} trail={this.props.trails[key]} />
))}
</div>
);
}
}
const trailStyle = {
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gridGap: "1rem",
};
export default Trails;
I feel like maybe I'm not using the correct syntax to step into the object, and then further into the "trails" array, but I'm stumped. Thank you for you help!
EDIT
Here is the "TrailItem" code:
import React from "react";
import PropTypes from "prop-types";
const TrailItem = ({ trail: { name, location, imgSmall } }) => {
return (
<div className='card text-center'>
<img src={imgSmall} alt='trail' style={{ width: "25%" }} />
<h3>{name}</h3>
<p>{location}</p>
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
export default TrailItem;
I followed the advice of one of the comments and changed the PropType to array, and that fixed one of the warnings. But I still can't get a list of <TrailItem />.
Inside you App render method put this at the start:
if (!this.state.trails.length) {
return <div>Loading...</div>;
}
You can give trails a default or initial value of an empty array so the map function can be invoked. By using an empty array the component will map over an the empty array and return an empty array to render.
Default Value:
const Trails = ({ trails }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
TrailItem.defaultValue = {
trail: [],
};
Initial Value:
const Trails = ({ trails = [] }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
Note: This won't fix passing a prop of the incorrect type, but the prop validation react does will. It sounds like you got that bit sorted out though.
I solved it! Granted, I may not have been clear in my original question, but I figured out why I couldn't get access to the data object from the API.
I needed to step into the object one more time upon receiving the response in my App component:
this.setState({ trails: res.data.trails });
Once I did that, in my Trails component I needed Object.key() to make turn the "trails" prop into an array so I could .map() over it.
And finally, the "tricky" part was that I needed to use each "key" as the index for each "trail" prop I was trying to pass to <TrailItem />:
{Object.keys(trails).map((key) => (
<TrailItem key={key} trail={trails[key]} />
))}
I'm passing a map to all my posts variable so that all my posts can appear as a single post, but it kept bringing up the error
I have tried solving it using the Reactjs Documentation from the react website, it shows almost the same code as mine.
import PropTypes from 'prop-types';
import PostItem from './PostItem';
class PostFeed extends Component {
render() {
const { posts } = this.props;
const list = posts.map((post) => <PostItem
key={post._id}
posts={post}
/>
);
return (
{list}
);
}
}
PostFeed.propTypes = {
posts: PropTypes.array.isRequired
};
export default PostFeed;
I expect every posts to appear as a single post from the postItem component
The error means, when your PostFeed mounts for first time at that time props are not available, so the error.
You can check if data available, like,
let list = "Loading...";
if(posts && posts.length > 0){
list = posts.map((post) => <PostItem key={post._id} posts={post} /> );
}
posts is probably result of an async action and its value is not available at the time that your function is doing its job. So it should have a default value or be checked before that has a value.do this:
if(this.props.posts && Array.isArray(this.props.posts) && this.props.posts.length > 0)
//then map
Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I Have an issue related to this message. This is my component that causes the error:
import React from 'react';
import Thumbnail from './Thumbnail';
const AlbumList = ({ results }) => {
let collections = [];
let albums = { };
Object.values(results).map((item, i) => {
if(!collections.includes(item.collectionId)) {
collections.push(item.collectionId);
albums[i] = {id: item.collectionId, cover: item.artworkUrl100}
}
return albums;
});
Object.values(albums).forEach(element => {
return (
<Thumbnail source={element.cover} />
)
})
}
export default AlbumList;
Thumbnail is just a basic Component like:
import React from 'react';
const Thumbnail = ({source}) => {
return(
<div>
<img src={source} alt="album cover"/>
</div>
)
}
export default Thumbnail;
I've been looking for the error like an hour or so.
What am I missing?
Notice that map returns a list while forEach returns undefined.
You don't return anything from your functional Component Plus forEach does not return anything, instead change it to map.
Also you need to set the unique key to Thumbnail component in loop
return Object.values(albums).map((element, index) => {
return (
<Thumbnail key={"Key-"+index} source={element.cover} />
)
})
I would suggest to read this article map vs. forEach