problem with rendering an object property from API object - javascript

I have requested API call in my Recipe App and have access to all the data I need in my front page but then when I try to to get specific properties from the API call in another page of my app I get an error once I try to render it.
I get an error telling me "Unable to get property 'label' of undefined or null reference"
could someone please help?
This is my code:
RecipeApp
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import Form from "./components/form";
import Recipes from "./components/Recipes";
import "./App.css";
export default class RecipeApp extends React.Component {
state = {
recipes: []
};
getRecipe = async event => {
event.preventDefault();
const recipeName = event.target.recipeName.value;
const API_CALL = await fetch(
`https://api.edamam.com/search?q=${recipeName}&app_id=${API_ID}&app_key=${API_KEY}&from=0&to=10&calories=591-722&health=alcohol-free`
);
const data = await API_CALL.json();
this.setState({
recipes: data.hits
});
console.log(this.state.recipes);
};
render() {
return (
<div className="App">
<header>
<h1>Find Your Recipe</h1>
</header>
<Form getRecipe={this.getRecipe} />
<Recipes
recipes={this.state.recipes}
/>
</div>
);
}
}
Router
import React from "react"
import { BrowserRouter, Switch, Route } from "react-router-dom";
import RecipeApp from "../recipe-app"
import Recipe from "./Recipe"
const Router = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={RecipeApp} exact/>
<Route path="/recipe/:label" component={Recipe} />
</Switch>
</BrowserRouter>
)
}
export default Router
Recipes
import React from "react"
import { Link } from "react-router-dom"
const Recipes = ({ recipes }) => (
<div id="container">
<div className="row">
{recipes.map( (item) => {
return(
<div key={item.recipe.label} className="col-md-4" style={{marginBottom: "2rem"}}>
<div id="item-box">
<img
src={item.recipe.image}
alt={item.recipe.label} />
<div id="item-text">
<h3>{item.recipe.label.length > 20 ? `${item.recipe.label}` : `${item.recipe.label.substring(0, 25)}...`}</h3>
<p><i>publisher: {item.recipe.source}</i></p>
</div>
<button>
<Link
to={ {
pathname: `/recipe/${item.recipe.label}`,
state: { recipe: item.recipe.label }
} }>view recipe
</Link>
</button>
</div>
</div>
)
})}
</div>
</div>
)
export default Recipes;
Recipe
import React from "react"
class Recipe extends React.Component {
state = {
activeRecipe: []
}
componentDidMount = async () => {
const title = this.props.location.state.recipe
const request = await fetch(
`https://api.edamam.com/search?q=${title}&app_id=${API_ID}&app_key=${API_KEY}&from=0&&calories=591-722&health=alcohol-free`
);
const response = await request.json();
this.setState({ activeRecipe: response.hits[0] })
}
render() {
const recipe = this.state.activeRecipe
console.log(recipe.recipe.label)
return(
<div></div>
)
}
}
export default Recipe
This is Error Message:
The above error occurred in the <Recipe> component:
in Recipe (created by Context.Consumer)
in Route (at Router.js:11)
in Switch (at Router.js:9)
in Router (created by BrowserRouter)
in BrowserRouter (at Router.js:8)
in Router (at src/index.js:7)
SCRIPT5007: SCRIPT5007: Unable to get property 'label' of undefined or null reference
main.chunk.js (115,5)
0: Unable to get property 'label' of undefined or null reference```

Because when component starts, the recipes state is = []. The API that you call to the server is Async. At the first time render of Component, this API still not yet return data for you, for the second time, you will got this data.
At here: {recipes.map( (item) => { })}, recipes empting in the first time render of Component, so when you access to item.recipe.label. You will got the error Unable to get property 'label' of undefined or null.
To resolve this, make sure recipes has data like this:
recipes.length > 0 && recipes.map( (item) => { ... })
Or you can use default parameter like this:
{recipes.map( (item) => {
const { recipe = {} } = item;
// use default parameter here to make sure recipe always is an object
// then, use recipe.label will be no errors
return(
<div key={recipe.label} className="col-md-4" style={{marginBottom: "2rem"}}>
...
</div>
)
})}

Related

TypeError: Cannot read properties of undefined (reading 'map') in react redux

I am trying to create a simple web application which lists products from fakestore api using REACT REDUX. But react is throwing the error "TypeError: Cannot read properties of undefined (reading 'map')".
productListing.jsx
import React, { useEffect } from 'react';
import axios from 'axios';
import { useDispatch, useSelector } from 'react-redux';
import { setProducts } from '../redux/actions/productActions';
import ProductComponent from './ProductComponent';
const ProductListing = () => {
// const products = useSelector((state) => state.allProducts.products);
const dispatch = useDispatch()
const fetchProducts = async () => {
const response = await axios
.get("https://fakestoreapi.com/products")
.catch((err) => {
console.log("Err: ", err);
});
dispatch(setProducts(response.data));
console.log(response.data)
};
useEffect(() => {
fetchProducts()
},[])
// console.log("Products :" , products)
return (
<div className="ui grid container">
<ProductComponent />
</div>
);
};
export default ProductListing;
The above component is responsible for api call from fakestoreapi and updating the redux store.
In the following component named "productComponent.jsx" i tried to list the products from the redux store using map method which is as follows :
import React from 'react'
import { useSelector } from 'react-redux'
const ProductComponent = () => {
const pdts = useSelector((state) => state.allProducts.products);
// console.log(typeof(pdts))
console.log("Products",pdts)
const renderList = pdts.map((pdt) => {
// const { id, title, image, price, category } = pdt;
return(
<div className="four column wide" key={pdt.id}>
<div className="ui link cards">
<div className="card">
<div className="image">
<img src={pdt.image} alt={pdt.title} />
</div>
<div className="content">
<div className="header">{pdt.title}</div>
<div className="meta price">$ {pdt.price}</div>
<div className="meta">{pdt.category}</div>
</div>
</div>
</div>
</div>
)
})
return(
<>{renderList}</>
// <>gbdf</>
)
}
export default ProductComponent
But React is throwing the folowing error :
Error Image
when i consoled the products object it shows undefined. But after I commented renderlist and again consoled the products object it consoled two time with the first one being undefined and second one printing the correct object with values. At this point of time I uncommented the render list, now the react is listing the products but when i again reload the page it consoles undefined value for two times.
Pdts is undefined in the first render. Hence cant be mapped.
Try this
pdts?.map()
It's known as Optional Chaining.

Next.js gow to fetch data and pass it to react dom in a custom App environment

In my Next.js project i've set up a custom App in _app.js since i'm using next-auth
_app.js is:
import React from "react"
import { Provider } from 'next-auth/client'
import '../public/styles.css'
// Use the <Provider> to improve performance and allow components that call
// `useSession()` anywhere in your application to access the `session` object.
export default function App ({ Component, pageProps }) {
return (
<Provider
options={{
clientMaxAge: 30 * 60,
keepAlive: 5 * 60
}}
session={pageProps.session} >
<Component {...pageProps} />
</Provider>
)
}
from my understanding I can't use getStaticProps or getServerSideProps since i'm using a custom _app.js, so how can I pass to the react return my fetched data if it's asyncronous?
For instance this below is mypage.js
import Layout from '../components/layout'
import { useSession } from 'next-auth/client'
import React from "react";
import InfiniteScroll from "react-infinite-scroll-component";
const siteurl = process.env.NEXT_PUBLIC_SITE_URL;
export default function Page() {
const [ session, loading ] = useSession()
// When rendering client side don't display anything until loading is complete
if (typeof window !== 'undefined' && loading) return null
// If no session exists, display access denied message
if (!session) { return <Layout>no data</Layout> }
if (session){
// Fetch content from protected profile api route
async function fetchVidsRequest() {
const response = await fetch(`${siteurl}/api/profile`);
const data = await response.json();
//console.log("DATA: "+JSON.stringify(data, null, 2));
return data;
}
const mainVid = async () => {
const result = await fetchVidsRequest()
return result
};
mainVid() // <--- this is a promise ! how can i pass it to the react code below?
return (
<Layout>
<div>
<h1>Latest videos</h1>
<hr />
<InfiniteScroll
dataLength={3} //This is important field to render the next data
next={data}
hasMore={true}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Loaded all videos</b>
</p>
}
>
{data.map((i, index) => (
<div key={index}>
<p>{i.dbsubs.inflVid[index]}</p>
</div>
))}
</InfiniteScroll>
</div>
</Layout>
)
}
}

Why is useEffect being triggered without dependency change when working with modals?

I'm having trouble working with useEffect to fetch comments when using a modal. I have a PostMain component that is displayed inside a modal, as seen below. Inside this, there is a CommentsList child component that fetches comments left under the post from the server. I have created a custom hook to handle this, as seen below. The problem I'm facing is whenever I exit the modal, then reopen it, useEffect is triggered even though its dependencies (pageNumber, postId) haven't changed. A server request similar to the initial one is sent, with the same comments being added to the list, as seen in the screenshots below. Obviously, this is not ideal. So, what am I doing wrong? How do I fix this?
Fetch Comments Custom Hook
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchComments } from '../store/comments/actions';
function useFetchComments(pageNumber, commentsPerRequest = 5, postId) {
const { error, hasMoreComments, isLoading, commentList } = useSelector(
({ comments }) => ({
error: comments.error,
hasMoreComments: comments.hasMoreComments,
isLoading: comments.isLoading,
commentList: comments.commentList,
})
);
const currentCommentListLength = commentList.length || 0;
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchComments(pageNumber, commentsPerRequest, currentCommentListLength, postId));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageNumber, postId]);
return {
error,
hasMoreComments,
isLoading,
commentList,
};
}
export default useFetchComments;
Post Component
import React from 'react';
import { useSelector } from 'react-redux';
import { Image, Modal } from 'semantic-ui-react';
import CommentForm from '../../forms/comment';
import CommentList from '../../shared/comment-list';
function PostMain({ post }) {
const { isLoggedIn } = useSelector(({ auth }) => ({
isLoggedIn: auth.isLoggedIn,
}));
return (
<Modal size="tiny" trigger={<Image src={post.url} />}>
<Modal.Content>
<div>
<Image src={post.url} />
<CommentList postId={post._id} />
{isLoggedIn && (
<CommentForm postId={post._id} />
)}
</div>
</Modal.Content>
</Modal>
);
}
export default PostMain;
Comment List Component
import React, { useState } from 'react';
import { useFetchComments } from '../../../hooks';
function CommentList({ postId }) {
const COMMENTS_PER_REQUEST = 5;
const [pageNumber, setPageNumber] = useState(1);
const { error, isLoading, commentList, hasMoreComments } = useFetchComments(
pageNumber,
COMMENTS_PER_REQUEST,
postId
);
const handleFetchMoreComments = () => {
setPageNumber((previousNumber) => previousNumber + 1);
};
return (
<div>
<div>
{commentList.map((comment) => (
<div key={comment._id}>{comment.body}</div>
))}
{hasMoreComments && (
<p onClick={handleFetchMoreComments}>View More</p>
)}
</div>
{isLoading && <p>Loading...</p>}
{error && <p>{JSON.stringify(error)}</p>}
</div>
);
}
export default CommentList;
First instance of opening modal
Second instance of opening modal

How to use a state from one class to another

So i'm currently working on a PokeDex using the PokeApi available online.
The code of the project is as follows:
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import "../ui/PokemonList.css";
import axios from "axios";
export const PokemonList = class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon/",
pokemon: null
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokemon: res.data["results"] });
console.log(res);
}
render() {
return <div></div>;
}
};
export const PokeList = () => {
return (
<React.Fragment>
{this.state.pokemon ? (
<section className="poke-list">
{this.state.pokemon.map(pokemon => (
<PokemonCard />
))}
</section>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
);
};
As you can see, I have declared a state in the PokemonList Component class, but then I try to call it further down within the variable PokeList. The issue is that the state is not being recognized in PokeList
(I get the error "TypeError: Cannot read property 'state' of undefined" )
How can I go about calling the state that's declared in the class above?
-------------------EDIT-------------------------------
Okay, so I realized something. I have a code for my Dashboard.js that displays my list. Code is as follows
import React, { Component } from "react";
import { PokeList } from "../pokemon/PokemonList";
export default class Dashboard extends Component {
render() {
return (
<div>
<div className="row">
<div className="col">
<PokeList />
</div>
</div>
</div>
);
}
}
When I change the code from PokeList to PokemonList. so it'd be
import React, { Component } from "react";
import { PokemonList } from "../pokemon/PokemonList";
export default class Dashboard extends Component {
render() {
return (
<div>
<div className="row">
<div className="col">
<PokemonList />
</div>
</div>
</div>
);
}
}
I think get a list of 20 pokemon from the Api from
console.log(this.state.pokemon);.
But since I'm not displaying PokeList on the dashboard, then none of the pokemon cards display.
Screenshot of console output
First of all functional components are stateless. If you need to maintain state use class components or hooks. You can't use the state of one component in another component, You have two options,
Create a parent-child relationship between those components
Use state management libraries(Redux, etc)
There's a little of confusion between your PokemonList and PokeList component. I believe that what you really are looking for is to have just one of those. If you mix the two, you can have a component that controls the view based on the state, in your case, the state is your Pokemon list.
I mixed the two here, so your render method renders "Loading Pokemon" until you get your response back from axios, then when the response is back, it gets that data, updates your state and the state update trigger a re-render.
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import axios from "axios";
class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon/",
pokemon: null
};
componentDidMount() {
axios.get(this.state.url).then(res => {
this.setState({ pokemon: res.data["results"] });
});
}
render() {
let pokemonList = <h1>Loading Pokemon</h1>;
const pokemons = this.state.pokemon;
if (pokemons) {
pokemonList = (
<section className="poke-list">
<ul>
{pokemons.map(pokemon => (
<PokemonCard pokemon={pokemon} />
))}
</ul>
</section>
);
}
return <React.Fragment>{pokemonList}</React.Fragment>;
}
}
export default PokemonList;
I also created a simple PokemonCard component where I list the result from the API, just to show you that that approach works.
import React from "react";
const pokemonCard = props => {
return (
<li key={props.pokemon.name}>
<a href={props.pokemon.url}>{props.pokemon.name}</a>
</li>
);
};
export default pokemonCard;
You can find the final code, with PokeList and PokemonList now combined into one component called PokemonList here:
Keep in mind that if your render function depends on a certain state, it's probably certain that you should have that state being managed in that component, or passed down from a parent component.
In your example, I noticed you set url inside your state. URL is really not something that will change. It's a constant,so you can easily remove that from your state and place it in a variable and just leave your pokemon list there.
For example:
const url = "https://pokeapi.co/api/v2/pokemon/";
state = {
pokemon: null
};
componentDidMount() {
axios.get(url).then(res => {
this.setState({ pokemon: res.data["results"] });
});
}
import React , { Component } from "react";
import axios from "axios";
//make it as class based component
export default class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon/",
pokemon: null
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokemon: res.data["results"] });
console.log(res);
}
render() {
//check your data here
console.log(this.state.pokemon)
{/*pass data to child*/}
return <div> <PokeList data = { this.state } /> </div>;
}
};
//export this component
export const PokeList = (props) => {
//check your data is coming or not
console.log(props.data)
//access your data from props
return (
<React.Fragment>
{props.data.pokemon ? (
<section className="poke-list">
{props.data.pokemon.map(pokemon => (
pokemon.name
))}
</section>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
);
};
You need iterate your your pokelist passing the result from your componentDidMount function to your child component as a prop , then receive your prop in the child component here it's a working codesandbox iterating your pokemon names in the pokeList child component

Error when I was trying to render a map of items

I am trying to figure out where my error is in my react js page
I have tried different things like changing it into a state component, return and render statements, etc. But its still giving me
"TypeError: Cannot read property 'map' of undefined
Recipes
src/components/Recipes.js:4
1 | import React from "react";
2 |
3 | const Recipes = (props) => (
4 |
5 | { props.recipes.map((recipe)=> {
6 | return (
7 |
View compiled"
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import InputForm from "./components/InputForm";
import Recipes from "./components/Recipes"
const API_KEY= "mykey";
class App extends Component {
state= {
recipes: []
}
getRecipe = async (e) => {
e.preventDefault();
const recipeName = e.target.elements.recipename.value;
const api_call = await fetch(`https://www.food2fork.com/api/search?key=${API_KEY}&q=${recipeName}&page=2`)
const data = await api_call.json();
this.setState({ recipes: data.recipes });
}
render() {
return (
<div className="App">
<header className="App-header">
React Cookbook
</header>
<InputForm getRecipe={this.getRecipe} />
<Recipes recipes={this.state.recipes} />
</div>
);
}
}
export default App;
Recipes.js
import React from "react";
const Recipes = (props) => (
<div>
{ props.recipes.map((recipe)=> {
return (
<div key={recipe.recipe_id }>
<img src={recipe.image_url} alt={recipe.title}/>
<h3>{ recipe.title }</h3>
</div>
)
})}
</div>
)
export default Recipes;
As Andrew notes in the comments, it sounds like the response from the server is undefined, and that's what is getting added to the recipes state object. You can check for this in the console. Are your API key and recipe name valid, for example? Are you accessing the correct part of the returned data?
As an aside, recipes in state is empty on the first render. You need to check for that possibility in your code. Here I've simply returned an empty div, but you could add a loading spinner or something there instead to provide useful feedback to the user.
render() {
const { recipes } = this.state;
if (!recipes.length) return <div />;
return (
<div className="App">
<header className="App-header">
React Cookbook
</header>
<InputForm getRecipe={this.getRecipe} />
<Recipes recipes={recipes} />
</div>
);
}

Categories

Resources