How to fetch nested data in react - javascript

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>

Related

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

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;
);

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.

Problem displaying an item according to the url - React

I have a question. I have a component that when entering /category/:categoryId is rendered doing a fecth to "api url + categoryId". My problem is that if I change from one category to another the page only changes if the useEffect is executed infinitely which generates problems to the view as seen below. How can I make it run once and when I change from /category/1 to /category/2 the useEffect is executed correctly?
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;
My router file:
<Route path="/category/:categoryId" component={Categories} />
This is the problem that is generated, there comes a time when a fetch is made to a previously requested category and then the new requested category is executed.
See my problem in video
You can simply add categoryId to useEffect array argument. Function inside the useEffect is called, only when categoryId changes
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[categoryId]);
you can not edit producto directly, you should use productos :
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto && producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;

Div appear and disappear onclick in React JS

I want to show the content on the cards (marked in red and named product.description in the code) on click but I don't know any way of doing it.
The content on the card is fetched from JSON-server.
Here is some code
The first js is used to fetch the data from the server and display
that on the page and the second one is the method of showing the data
on the page. In general, I want the p tag to appear and disappear
when the user clicks on the main div named Product-Preview
import { useEffect, useState } from "react";
import ProductList from "./ProductList";
const Products = () => {
const [products, setProducts] = useState (null);
useEffect (() => {
fetch('http://localhost:8000/products')
.then(res => {
return res.json();
})
.then(data => {
setProducts(data);
})
}, []);
return (
<div className="ProductList">
{products && <ProductList products={products}/>}
</div>
);
}
export default Products;
const ProductList = (props) => {
const products = props.products;
return (
<div className="ProductList" >
{products.map((product) => (
<div className="Product-Preview" key={product.id}>
<div className="backdrop" style={{backgroundImage: `url(${product.image})`}}></div>
<h2>{ product.title }</h2>
<p>{ product.description }</p>
<div>{ product.price }</div><br />
</div>
))}
</div>
);
}
export default ProductList;
You should create a component and use a state in this component to do it.
Example component has a name is Card
const Card= ({ product }) => {
const [showDescription, setShowDescription] = useState(false);
return (
<div
className="Product-Preview"
onClick={() => setShowDescription(!showDescription)}
>
<div className="backdrop" style={{ backgroundImage: `url(${product.image})` }}></div>
<h2>{product.title}</h2>
{showDescription && <p>{product.description}</p>}
<div>{product.price}</div>
<br />
</div>
);
};
And use this component inside map
{
products.map((product) => <Card product={product} key={product.id} />);
}

UseEffect API Request not displaying on page load

Apologies in advance if similar questions like this have already been answered. I have tried everything, but still cannot figure out why I am experiencing this small bug.
I want this collection of tweets from my Firestore to render on the page when it loads. Right now, it only happens after I make a post request.
This is my request
useEffect(() => {
const getTweets = async () => {
const tweets = await firestore.collection('tweet').get();
tweets.forEach(doc => {
results.push(doc.data());
})
}
getTweets()
}, [])
This is where I'm mapping it to the page:
return (
<>
<main className="tweet-container">
<div className="tweet-container--content">
{results.map((tweet, index) => (
<InputResults
key={index}
tweet={tweet}
/>
))}
</div>
</main>
</>
)
}
Thank you so much
Try something like this,
import React, {useState, useEffect} from "react";
function App() {
const [results, setResults] = useState([]);
useEffect(() => {
const getTweets = async () => {
const tweetsData = [];
const tweets = await firestore.collection('tweet').get();
tweets.forEach(doc => {
tweetsData.push(doc.data());
})
setResults(tweetsData);
}
getTweets()
}, [])
return (
<>
<main className="tweet-container">
<div className="tweet-container--content">
{results.map((tweet, index) => (
<h1>{tweet}</h1>
))}
</div>
</main>
</>
)
}
Reference: https://reactjs.org/docs/hooks-state.html
Always add a key property to the elements when used a map function to show them on UI .
As it would help react to differentiate between the elements .
return (
<>
<main className="tweet-container">
<div className="tweet-container--content">
{results.map((tweet, index) => (
<h1 key={index} >{tweet}</h1>
))}
</div>
</main>
</>
)

Categories