How to solve the problem with 'map' function on React? - javascript

I'm doing project on React.js. I'm mapping the array and the error saying that the array is undefine even if it exists
<ul>
{details.extendedIngredients.map(ingredient => (
<li id={ingredient.id}>{ingredient.original}</li>
))}
</ul>
Full code:
import { useEffect, useState } from "react";
import styled from "styled-components";
import { useParams } from "react-router-dom";
function Recipe() {
let params = useParams();
const [details, setDetails] = useState({});
const [activeTab, setActiveTab] = useState("instructions");
const fetchDetails = async () => {
const data = await fetch(
`https://api.spoonacular.com/recipes/${params.name}/information?apiKey=${process.env.REACT_APP_API_KEY}`
);
const detailData = await data.json();
setDetails(detailData);
};
useEffect(() => {
fetchDetails();
}, [params.name]);
console.log(details.extendedIngredients);
return (
<DetailWrapper>
<div>
<h2>{details.title}</h2>
<img src={details.image} alt="" />
</div>
<Info>
<Button
className={activeTab === "instructions" ? "active" : ""}
onClick={() => setActiveTab("instructions")}
>
Instructions
</Button>
<Button
className={activeTab === "ingredients" ? "active" : ""}
onClick={() => setActiveTab("ingredients")}
>
Ingredients
</Button>
<div>
<h3 dangerouslySetInnerHTML={{ __html: details.summary }}></h3>
<h3 dangerouslySetInnerHTML={{ __html: details.instructions }}></h3>
</div>
<ul>
{details.extendedIngredients.map(ingredient => (
<li id={ingredient.id}>{ingredient.original}</li>
))}
</ul>
</Info>
</DetailWrapper>
)}
export default Recipe;

As setDetails supposed to save the details received from your API in an array, I guess that it must be initialised as an empty array
const [details, setDetails] = useState({});
As it will be an empty array, there will be no render when the component will be mounted from react.
Should be:
const [details, setDetails] = useState({});

edit this three parts:
first Part:
useEffect(() => {
fetchDetails().then(res=>
{setDetails(res.data); console.log(res)}
);
}, []);
second Part:
<ul>
{details?.extendedIngredients?.map(ingredient => (
<li id={ingredient.id}>{ingredient.original}</li>
))}
</ul>
third Part:
const fetchDetails = async (params) => {
const data = await fetch(
`https://api.spoonacular.com/recipes/${params.name}/information?
apiKey=${process.env.REACT_APP_API_KEY}`
return data;
);

Related

How to add a "show more" button to each card on React?

I have React component:
Main.jsx
import { useState, useEffect } from "react";
import { Preloader } from "../Preloader";
import { Pokemons } from "../Pokemons";
import { LoadMore } from "../LoadMore";
function Main() {
const [pokemons, setPokemons] = useState([]);
const [loading, setLoading] = useState(true);
const [pokemonsPerPage] = useState(20);
const [page, setPage] = useState(1);
function getPokemons(pokemonOffset) {
fetch(
`https://pokeapi.co/api/v2/pokemon?limit=${pokemonsPerPage}&offset=${pokemonOffset}`
)
.then((responce) => responce.json())
.then((data) => {
data.results && setPokemons((p) => [...p, ...data.results]);
setLoading(false);
});
}
useEffect(() => {
const offset = page * pokemonsPerPage - pokemonsPerPage;
getPokemons(offset);
}, [page]);
return (
<main className="container content">
{loading ? <Preloader /> : <Pokemons pokemons={pokemons} />}
<LoadMore next={() => setPage((p) => p + 1)} />
</main>
);
}
export { Main };
Pokemon.jsx
import { useState, useEffect } from "react";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
return (
<div>
{data ? (
<div className="card animate__animated animate__fadeIn">
<div className="card-image">
<img src={data.sprites.front_default} />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
) : (
<div>loading...</div>
)}
</div>
);
}
export { Pokemon };
I need each card (Pokemon) to have a "Details" button, which, when clicked, displays additional (unique) information from the fetch request in the "url" for the selected card
I think I need to do this in Pokemon.jsx but I just started learning React and haven't come across a similar challenge
If you just need a button for each card I would assume this
{data.map((item, index) =>
<div key={index}>
....
<button onClick={()=> { do something }}>
</div>
)}
and then create a function that fetches data and add it to your array where you keep
the data and might have to mess with the useEffect when you want to see the change.

How to fetch nested data in react

Question regarding fetching nested data in react.
APIs
https://jsonplaceholder.typicode.com/posts
https://jsonplaceholder.typicode.com/posts/${postId}/comments
Able to fetch list of posts. now want to fetch list of comments from when click on post
here is code so far
import React, { useEffect, useState } from "react";
import Post from "./Post";
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
function App() {
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
useEffect(() => {
const loadposts = async() => {
const resp = await fetch("https://jsonplaceholder.typicode.com/posts?userId=1");
const data = await resp.json();
setPosts(data);
}
loadposts();
}, []);
return (
<div className="App">
<ul>
{posts.map((post) =>
(
<div>
<li key={post.id}>
<Post
userId={post.id}
title={post.title}
body={post.body}
/>
</li>
</div>
))
}
</ul>
</div>
);
}
export default App;
function Post({title, body, postId}) {
return (
<div>
<h5>{postId}</h5>
<h1>{title}</h1>
<p>{body}</p>
</div>
)
}
export default Post
appreciate any help. thanks
Firstly, the "/posts" endpoint returns posts by users, so the query "/posts?userId=1" will return all the posts by user id 1. You mistakenly passed a userId prop to the Post component instead of the specific post's id, i.e.
<Post userId={post.id} title={post.title} body={post.body} />
The React key should also be placed on the outer-most element being mapped, the div in your case, but since li is already a block level element the div is basically extraneous.
<ul>
{posts.map((post) => (
<li key={post.id}> // <-- remove div and place React key on li
<Post
postId={post.id} // <-- pass the post's id
title={post.title}
body={post.body}
/>
</li>
))}
</ul>
In Post component create a fetch comments utility and click handler, and attach the click handler to the title header. Conditionally render the comments. If it wasn't already clear, you'll move the comments state into Posts so each post component maintains its own copy. The following is an example for rendering out the comments once fetched, you can use whatever conditional rendering and field subset of your choosing.
const fetchComments = async (postId) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${postId}/comments`
);
return response.json();
};
function Post({ title, body, postId }) {
const [comments, setComments] = useState([]);
const clickHandler = () => {
fetchComments(postId).then(setComments);
};
return (
<div>
<h5>{postId}</h5>
<h1 onClick={clickHandler}>{title}</h1>
<p>{body}</p>
{comments.length && (
<>
Comments:
<ul>
{comments.map(({ id, email, name, body }) => (
<li key={id}>
<dl>
<dt>{email} - {name}</dt>
<dd>{body}</dd>
</dl>
</li>
))}
</ul>
</>
)}
</div>
);
}
Working solution if anyone looking for
function Post() {
const {id} = useParams();
const [comments, setComments] = useState([]);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}/comments`)
.then((res) => res.json())
.then(setComments)
.catch((error) => {
console.log(error)
})
console.log("setComments: ", setComments)
}, [])
return (
<div>
{comments && comments.map((comment) => (
<div key={comment.id}>
<p>{comment.body}</p>
</div>
))}
</div>
)
}
export default Post
then update rendering
<div className="App">
<Switch>
<Route exact path='/'>
{posts.map((post) => (
<article key={post.id}>
<h1>{post.title}</h1>
<Link to ={`/${post.id}`}>
<p>{post.body}</p>
</Link>
</article>
))}
</Route>
<Route path ='/:id'>
<Post/>
</Route>
</Switch>
</div>

Custom pagination using ReactJS

I have this project for pagination of json data received through an API. The problem is that my code somehow gives me a 'slice' error (it is not the case when using other API's, e.g. https://corona.lmao.ninja/v2/countries) <--- Works fine
Items.js:
import React from 'react';
import { ITEMS_PER_PAGE } from '../utils/constants';
import Data from './Data';
const Items = ({ items, page }) => {
const startIndex = (page - 1) * ITEMS_PER_PAGE;
const selectedItems = items.slice(startIndex, startIndex + ITEMS_PER_PAGE);
return (
<React.Fragment>
{selectedItems.map(item => (
<Data key={item.country} {...item} />
))}
</React.Fragment>
);
};
export default Items;
Data.js:
import React from 'react';
const Data = ({ Data }) => {
const { high, low } = Data;
return (
<div class="data">
<p>
<strong>Test:</strong> {high} {low}
</p>
<hr />
</div>
);
};
export default Data;
Pagination.js:
import React from 'react';
const Pagination = ({ totalPages, handleClick, page }) => {
const pages = [...Array(totalPages).keys()].map(number => number + 1);
return (
<div className="numbers">
{pages.map(number => (
<a
key={number}
href="/#"
onClick={() => handleClick(number)}
className={`${page === number && 'active'}`}
>
{number}
</a>
))}
</div>
);
};
export default Pagination;
App.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Pagination from './components/Pagination';
import Items from './components/Items';
import { ITEMS_PER_PAGE } from './utils/constants';
const App = () => {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
axios
.get('https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=10')
.then(response => {
const result = response.data;
setItems(result);
setTotalPages(Math.ceil(result.length / ITEMS_PER_PAGE));
setIsLoading(false);
});
}, []);
const handleClick = number => {
setPage(number);
};
return (
<div>
<h1>Pagination Demo</h1>
{isLoading ? (
<div className="loading">Loading...</div>
) : (
<React.Fragment>
<Items items={items} page={page} />
<Pagination
totalPages={totalPages}
handleClick={handleClick}
page={page}
/>
</React.Fragment>
)}
</div>
);
};
export default App;
My problem seems to be something that am I missing with this other API: https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=10
error: TypeError: items.slice is not a function in Items.js
Any help would be appreciated!
The response from the API has 2 nested Data keys, so it has to be like this:
const result = response.data;
setItems(result.Data.Data);
Data.js
import React from 'react';
const Data = ({ high, low }) => {
return (
<div class="data">
<p>
<strong>Test:</strong> {high} {low}
</p>
<hr />
</div>
);
};
export default Data;
demo: https://stackblitz.com/edit/react-arqaxj

How to iterate through Redux State object

As soon as my app loads the 'FETCH_PRODUCTS' action payload is dispatched, resulting in items sourced from the data.json file, being added to state in my products reducer. I am able to access this state
via console.log(action.payload) in both my actions and reducers files. I need to be able to iterate through the state object so I can render products from state in my Products component. However, I am unable to iterate thru the object. Ive tried with no luck, to convert it to an iterable array with Object.keys(), .values() and .entries().
This is what I get when console.log() action.payload or products in Products.js
Products.js
import React, { useState, useEffect } from "react";
import { Fade, Zoom } from "react-reveal";
import Modal from "react-modal";
import { connect, useDispatch } from "react-redux";
import fetchProducts from "../actions/productActions";
const Products = ({ products, add }) => {
const [product, setProduct] = useState(null);
const openModal = (product) => {
setProduct(product);
};
const closeModal = () => {
setProduct(null);
};
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
console.log(products)
const renderProducts = () => {
return products.map((product) => {
return (
<li key={product._id}>
<a href={"#" + product._id} onClick={() => openModal(product)}>
<img src={product.image} />
</a>
<p>
{product.title}
</p>
<p>
<strong>${product.price}.00</strong>
</p>
<button onClick={() => add(product)}>ADD TO CART</button>
</li>
);
});
};
return (
<div>
<Fade bottom cascade>
<div>Loading...</div>
<ul>{!products ? <div>Loading...</div> : renderProducts()}</ul>
</Fade>
{product && (
<Modal isOpen={true}>
<Zoom clear cascade>
<div>
<p onClick={() => closeModal()}>X CLOSE</p>
<img src={product.image} />
</div>
<div>
<p>{product.title}</p>
<p>${product.price}.00</p>
Sizes
<p>
Available Sizes
{product.availableSizes.map((size) => {
return (
<>
<br />
<span> {size} </span>
</>
);
})}
</p>
<button
onClick={() => {
add(product);
closeModal();
}}
>
ADD TO CART
</button>
</div>
</Zoom>
</Modal>
)}
</div>
);
};
export default connect((state) => ({ products: state.products.items }), {
fetchProducts,
})(Products);
productActions.js
import { FETCH_PRODUCTS } from "../components/types";
const fetchProducts = () => async(dispatch) => {
const result = await fetch('data/data.json');
const data = await result.json();
console.log(data);
dispatch({
type: FETCH_PRODUCTS,
payload: data
})
}
export default fetchProducts
productReducers.js
const { FETCH_PRODUCTS } = require("../components/types");
const data = require('../data.json')
const productsReducer = (state = {}, action) =>{
switch(action.type){
case FETCH_PRODUCTS:
console.log(action.payload)
return {items: action.payload}
default:
return state;
}
}
export default productsReducer;
store.js
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from "redux-thunk";
import productsReducer from "./reducers/productReducers";
const initialState = {};
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
combineReducers({ products: productsReducer }),
initialState,
composeEnhancer(applyMiddleware(thunk))
);
export default store;
You aren't accessing the data, because you didn't get the property from the object
Your redux store for products says this whenever you console.log(products)
{products : Array(6)}
So as a result you have to say products.products to properly map the array
Addressing undefined value
The problem is you are instantly returning the component when there is no data
const renderProducts = () => {
return products.map((product) => {
return (
<li key={product._id}>
<a href={"#" + product._id} onClick={() => openModal(product)}>
<img src={product.image} />
</a>
<p>
{product.title}
</p>
<p>
<strong>${product.price}.00</strong>
</p>
<button onClick={() => add(product)}>ADD TO CART</button>
</li>
);
});
};
Keep in mind that getting data takes time so you have to check if your products state is empty. This can be solve with a ternary operator:
return (
<div>
{doSomething ? something : null}
</div>
);
So in your case check if the products array is empty. If yes map through the array. If no then return "no products".
return (
<div>
{products.products.length !== 0 ? <your regular routine as above) : <p>No products</p>}
</div>
);
Finally figured it out!!! Needed to change payload: data to payload: data.products in productActions.js

Display data from jsonplaceholder api using react hooks

I have simple rest api, I am trying to display users from jsonplaceholder fake api
Here is my function component
import React, {useState, useEffect} from "react";
import axios from 'axios';
export default function TableList() {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://jsonplaceholder.typicode.com/users',
);
setData(result.data);
console.log(result.data);
};
fetchData();
}, []);
return (
<div>
<ul>
{data.hits.map(item => (
<li key={item.id}>
<h1>{item.name}</h1>
</li>
))}
</ul>
</div>
);
}
Unfortunately, I am getting the following error:
TableList.js:22 Uncaught TypeError: Cannot read property 'map' of undefined
What is wrong with my code?
You're setting the data incorrectly and you should null check data.hits. Here's a working example
function TableList() {
const [users, setUsers] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const { data } = await axios(
"https://jsonplaceholder.typicode.com/users"
);
setUsers({ hits: data });
};
fetchData();
}, [setUsers]);
return (
<div>
<ul>
{users.hits &&
users.hits.map(item => (
<li key={item.id}>
<span>{item.name}</span>
</li>
))}
</ul>
</div>
);
}
https://codesandbox.io/s/affectionate-lehmann-17qhw
"hits" is necesary?.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function MiJSON(){
const [users, setUsers] = useState([]);
const urlJson= 'https://jsonplaceholder.typicode.com/users';
useEffect(()=>{
const fetchData = async ()=>{
const users_data = await axios(urlJson);
setUsers(users_data);
};
fetchData();
},[setUsers])
console.log(users)
return (
<div>
<h1>USERS</h1>
<ul>
{(users.length !== 0)
?
users.data.map(item => (
<li key={item.id}>
<span>{item.name}</span>
</li>
))
: <h1>Sorry info not found</h1>
}
</ul>
</div>
)
}

Categories