Fetching API data, nested in two arrays and displaying it with React - reactjs

I am trying to fetch data that is nested in two unnamed arrays API Endpoint.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const API_URL = 'https://my-json-server.typicode.com/TomSearle/cb-devtest-api/products';
const MyComponent = () => {
const [posts, setPosts] = useState([]);
const fetchData = async () => {
const { data } = await axios.get(API_URL);
setPosts(data);
console.log(data);
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
{posts.length > 0 ? (
<div>
{posts.map((post) => (
<div>
<h2>{post.price}</h2>
<p>{post.stock_count}</p>
</div>
))}
</div>
) : (
<p className="loading">Loading... </p>
)}
</div>
);
};
export default MyComponent;
console.log shows an Array with 10 Objects, how could I destructure that data to display it dynamically? Any help would be appreciated.

Your array is nested one more level somehow. Better to fix it in the backend or simply access the posts like below.
{
posts[0].map((post) => (
<div>
<h2>{post.price}</h2>
<p>{post.stock_count}</p>
</div>
))
}
Working Demo

Related

Unable to set posts (posts is a state using useState) after fetching them successfully in useEffect - React 18.2.0

I have successfully fetched posts data from "https://jsonplaceholder.typicode.com/posts" inside useEffect hook that is response is successfully logged to console with data property which contains data (posts) and similarly response.data is also logged to the console with actual posts data but I am unable to set the fetched posts to posts state by setting it using setPosts setter provided by useState hook When I log posts to the console after successfully fetching the posts, my posts state is is empty array [] and also UI doesn't show any posts.
I am using React v 18.2.0 can anyone help me about this? Below is my code!
import './App.css';
import { useEffect, useState } from 'react';
import axios from 'axios';
function App() {
const [posts, setPosts] = useState([])
useEffect(() => {
(async () => {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts")
console.log(response)
const data = response.data
// data contains all the posts which are successfully logged
console.log(data)
// Here posts is logged as empty array [].........why?
setPosts(data)
console.log("posts are", posts)
})()
},[])
return (
<div className="App">
{posts.map(post => {
<>
<li key={post.id}> {post.title}</li>
<p>{post.body}</p>
</>
})}
</div>
);
}
export default App;
I have successfully fetched posts data from "https://jsonplaceholder.typicode.com/posts" inside useEffect hook that is response is successfully logged to console with data property which contains data (posts) and similarly response.data is also logged to the console with actual posts data but I am unable to set the fetched posts to posts state by setting it using setPosts setter provided by useState hook When I log posts to the console after successfully fetching the posts, my posts state is is empty array [] and also UI doesn't show any posts.
You have missed return part in jsx
import "./App.css";
import { useEffect, useState } from "react";
import axios from "axios";
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
(async () => {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
console.log(response);
const data = response.data;
// data contains all the posts which are successfully logged
console.log(data);
// Here posts is logged as empty array [].........why?
setPosts(data);
console.log("posts are", posts);
})();
}, []);
return (
<div className="App">
{posts.map((post) => {
return (
<>
<li key={post.id}> {post.title}</li>
<p>{post.body}</p>
</>
);
})}
</div>
);
}
export default App;
OR more better
<div className="App">
{posts.map((post) => (
<>
<li key={post.id}> {post.title}</li>
<p>{post.body}</p>
</>
))}
</div>
setposts is an asynchronous function, you are printing the values before they are updated. And you should use the return keyword in the arrow function body in curly brace {}, or you should wrap the return value in parenthesis (). The code will look like:
import { useEffect, useState } from "react";
import axios from "axios";
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
(async () => {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
const data = response.data;
setPosts(data);
})();
}, []);
return (
<div className="App">
{posts.map((post, index) => (
<>
<li key={post.id}> {post.title}</li>
<p >{post.body}</p>
</>
))}
</div>
);
}
export default App;
You can check the live demo here: https://codesandbox.io/s/nostalgic-tdd-hwdjxk?file=/src/App.js:0-646

Problem when rendering image in React component

I´m getting an error when trying to render an image in component.
I paste the code here.
Is it possible that I need a babel or webpack plugin?
In this component, the image rendering works fine:
import React from "react";
function ProductItem({ product }) {
return product ? (
<div>
<div>
<img src={product.images[0]} alt={product.title} />
</div>
<div>
{product.title}
<br />
${product.price}
</div>
<p>{product.description}</p>
</div>
) : <p>Loading Product... </p>;
};
export default ProductItem;
In this other component is where I have the problem.
ProductDetail.js
import React from "react";
import useGetProducts from "../hooks/useGetProducts";
const API = 'https://api.escuelajs.co/api/v1/products';
function ProductDetail() {
const data = useGetProducts(`${API}/6`);
return (
<>
{data.products
?
<>
<h3>{data.products.title}</h3>
<p>{data.products.description}</p>
<div>
<img src={data.products.images[0]} alt="title" />
</div>
</>
: <h4>Loading...</h4>
}
</>
);
}
export default ProductDetail;
Custom Hook with useEffect, the useGetProducts function is responsible for bringing the data from the API with the Axios library
import { useEffect, useState } from "react";
import axios from "axios";
const useGetProducts = (API) => {
const [products, setProducts] = useState([])
const [error, setError] = useState("");
const [loaded, setLoaded] = useState(false);
useEffect(() => {
(async () => {
try {
const response = await axios(API);
setProducts(response.data);
} catch (error) {
setError(error.message);
} finally {
setLoaded(true);
}
})();
}, []);
return { products, error, loaded };
};
export default useGetProducts
Your default state for products is [], so the conditional render data.products in ProductDetail.js always return true so you can change default state for products is null
const [products, setProducts] = useState(null);
The first answer is correct, so I will not duplicate it, but I see room for improvement in your code/example.
Your useGetProducts hook is very easy to break and hard to reuse. If you will pass the wrong URL or the structure of the API will change it will break your code. Also, the hook is not very generic, cause you will need to create similar fn for each entity. My suggestion. Use react-query and separate functions for calling API. So it will look like this.
import { useQuery } from 'react-query'
import axios from 'axios'
export default function ProductPage() {
const productResponse = useQuery('exchanges', () => getProduct('6'))
const { isLoading, isError, data: product } = productResponse
return (
<div>
{isLoading && <div>Loading...</div>}
{isError && <div>Something went wrong :(</div>}
{product && (
<div>
<h1>Product title: {product.title}</h1>
<p>
{product.images.map(imageSrc => (
<img key={imageSrc} src={imageSrc} alt="" />
))}
</p>
</div>
)}
</div>
)
}
interface Product {
id: string
title: string
images: string[]
}
function getProduct(id: string): Promise<Product> {
return axios
.get(`https://api.escuelajs.co/api/v1/products/${id}`)
.then(r => r.data)
}
PS. react-query requires additional configuration ( context provider, config, etc ). Please look into docs on how to use it.

React: How to get data from Axios, not Promise?

I'm trying to get data from a link but all I get is a Promise.
Here is my example code:
import axios from "axios";
export default function App() {
const url = `https://finnhub.io/api/v1/stock/profile2?symbol=GME&token=c6a500qad3idi8g5o2v0`;
const fetchData = async (u) => {
return await axios.get(u).then((res) => res.data);
};
return (
<div className="App">
<button onClick={() => console.log(fetchData(url))}>click me</button>
</div>
);
}
I don't know which part of the code is wrong that it keeps giving out the Promise like the photo here:
Please help. I appreciate it!
You can do it like this
import axios from "axios";
export default function App() {
const url = `https://finnhub.io/api/v1/stock/profile2?symbol=GME&token=c6a500qad3idi8g5o2v0`;
const [data, setData] = React.useState(null);
const fetchData = async (u) => {
return await axios.get(u).then((res) => {
setData(res.data);
console.log(res.data.name);
});
};
return (
<div className="App">
<button onClick={() => fetchData(url)}>click me</button>
Name :{data?.name}
</div>
);
}

rendering sorted array of objects using UseMemo

I'm trying to render the sorted array of objects using useMemo. Currently the last sorted array is rendering on the screen. But i want to use the select drop down where users can select different sort like title shown in code using useMemo. The users can sort by selecting title, author image.
I have used redux for sorting the array of objects.Could someone please help me with the best practice. Thanks.
I have added Post.js below the HomePage.js. Is my approach to it is wrong? Should i change the approach?
Any suggestions will be helpful.Could someone suggest me the best practies for it. Any suggestions on what am i doing wrong here?
HomePage.js
import React, { useState, useEffect, useMemo } from "react";
import Post from "../../Components/Post/Post";
import "./HomePage.css";
import axios from "axios";
const HomePage = () => {
const [posts, setPosts] = useState("");
let config = { Authorization: "................" };
const url = ".........................";
useEffect(() => {
AllPosts();
}, []);
const AllPosts = () => {
axios
.get(`${url}`, { headers: config })
.then((response) => {
const allPosts = response.data.articles;
console.log(response);
})
.catch((error) => console.error(`Error: ${error}`));
};
const newPostsByTitle = useMemo(() => {
allPosts.sort((a, b) => a.title.localeCompare(b.title)), [posts];
});
return (
<div className="home">
<div className="select">
<select
name="slct"
id="slct"
onChange={(e) => newPostsByTitle(e.target.value)}
></select>
</div>
<Post className="Posts" posts={posts} key={posts.title} />
</div>
);
};
export default HomePage;
Post.js
import React from "react";
import "./Post.css";
import { Fragment } from "react";
const Post = (props) => {
const displayPosts = (props) => {
const { posts } = props;
if (posts.length > 0) {
return posts.map((post) => {
return (
<Fragment>
<div className="Post" key={post.title}>
<img
src={post.urlToImage}
alt="covid"
width="100%"
className="img"
/>
<h5 className="title"> {post.title}</h5>
<p className="author"> {post.author}</p>
<p className="description"> {post.description}</p>
</div>
</Fragment>
);
});
}
};
return <div className="Posts">{displayPosts(props)}</div>;
};
export default Post;
There are few issues with the useMemo function.
There is no allPosts variable that will be available for that
function
There is no return inside useMemo
The dependency array syntax is wrong.
It should be something like the following.
const newPostsByTitle = useMemo(() => {
return [...posts].sort((a, b) => a.title.localeCompare(b.title));
}, [posts]);

React Component State and API fetch

I have been studying React for past few days. In my blog project, I am using Axios to get data from API. Here is my component:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState({});
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
Above code works fine though I noticed the first time it tries to render the component with empty ({}) post object (Due to the default value in 'useState'). However, it causes issues in my child component cause it is directly using 'post' object properties. For example: 'post.content'. Here is my 'SinglePost' component's code:
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>{post.content}</div>
);
};
It returns undefined error for {post.content} object. To resolve the issue I had to use something like {post && <SinglePost post={post} />}, But it doesn't feel right. Is there any better way to handle such scenarios.
Consider revising the PostContent component's rendering logic to account for the case where no post data is present during the network request.
You could for instance initialise your post state to null, and then update the rendered result to prevent the SinglePost component from being rendered while post is null.
Once the network request is completed and the post state is set, the component will re-render causing SinglePost to be rendered with the non-null post state:
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState(null); /* Update: "Initial data" set to null */
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
{ /* <React.Fragment> Not needed */ }
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{ /* Update: If post is null, assume network request is
still in progress so rendering loading message instead
of single post component */ }
{ post === null ? <p>Loading</p> : <SinglePost post={post} /> }
</div>
</div>
</div>
);
};
export default PostContent;
This approach is generally the simplest and more common pattern to async requests and rendering.
There are some other approaches that you might want to consider such as this declarative approach to data fetching, or the use of Reacts suspense feature for asynchronous rendering
You need to make post's initial value an array:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
and in single post map through the array
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>
{post.map((post, key) => (
<div key={key}>{post.content}</div>
))}
</div>
);
};
You can do something like
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPosts(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{this.state.posts.map(post => (<SinglePost post={post} key={post.id} />))
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;

Resources