rendering sorted array of objects using UseMemo - reactjs

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

Related

React Image Gallery by categories - trying to separate the image component

I have 3 categories:
-Books
-Albums
-Movies
what I had: I had fetch and return in each component the same code (with different category, id and images), so basically decided to separate it to external component, everything works beside the url which for some reason I can't pass the variable "type" from grandparent to a grandchild and that's why images don't render. This is the code:
first child:
import React, { useState, useEffect } from "react";
export const useFetch = (address, view) => {
const [data, setData] = useState([]);
const [error, setError] = useState();
const fetchData = async (view) => {
await fetch(address)
.then((res) => res.json())
.then((json) => {
const data = json.map((elem) => {
console.log(view)
const imageUrl = `${view}${elem.id}`
const newObject = {
id: elem.id,
title: elem.title,
image: imageUrl,
};
return newObject;
});
return data;
})
.then((finalData) => {
setError(undefined);
setData(finalData);
})
.catch((e) => {
console.error("Errors: ", e);
setError(e);
});
}
useEffect(() => {
fetchData();
}, [address]);
return {data}
}
parent:
import React, { useState, useEffect } from "react";
import { useFetch } from "./useFetch"
function ImagesList({category, number, type}) {
const address = `https://jsonplaceholder.typicode.com/albums/${number}/photos`
const {data} = useFetch(address)
const view = type
return (
<div>
<h1 className="header__title"> {category}</h1>
<h1 className="header__title"> {eleven}</h1>
{/* {error && <p>Error!: {error.message}</p>} */}
<main className="container">
<div className="items-list" data-style="cards">
{data.map((item) => (
<div className="items-list__item item">
<img className="item__img" src={item.image}
/>
<div className="item__info">
<div className="caption">
<h6 className="item__name text--center">{item.title}</h6>
<div className="item__description">Statham stars as Arthur Bishop, a professional assassin who specializes in making his hits look like accidents, suicides, or the acts of petty criminals.</div>
<p className=" heart d-inline-block"> <button>"To Favorites"</button> </p>
</div>
</div>
<div>
</div>
</div>
// </div>
))}
</div>
</main>
</div>
)
}
export default ImagesList
and this is the main component (I have 3 of those total: Movies, Books and Albums, so I need to modify variables to pass them to the 2 above components)
the main one:
import React, { useState, useEffect, useContext } from "react";
import "../styles/images.css";
import "../styles/favorites.css";
import ImagesList from "./ImagesList";
const Books = (category, number) => {
return (
<ImagesList category="Books" number="2" type="https://api.lorem.space/image/book?w=150&h=220&hash=$"/>
);
}
export default Books;
Can anybody please help me? I am kind of stuck.
Thank you!

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.

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

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

How can I send a request to API with on click of my button in React hooks?

So I currently have this code that has a useEffect() that shows the data from the API every time. I refresh, but I'm trying to make that data display only when I click on my button. I'm not too sure where to go with my code.
import React, { useState, useEffect } from 'react';
import './App.css';
import axios from 'axios';
function App() {
const [image, setImage] = useState(false);
// I tried to put a onclick function, but not sure what to add here
const handleChange = ()
=> {
setImage(true)
}
// this code displays my data on refresh
useEffect(() => {
axios
.get(
'https://api.com'
)
.then(res => {
setImage ?
setImage(res.data.faces[0].urls[4][512]) : console.log('nothing')
})
.catch(err => {
console.log(err.message);
});
}, []);
return (
<div className='App'>
<h1>Photo Generator</h1>
<img src={image} />
<button onClick={handleChange}>Show new Image</button>
</div>
);
}
I've updated your code.
Try this code, let me know if it works for you. :)
import React, { useState } from "react";
import "./App.css";
import axios from "axios";
function App() {
const [image, setImage] = useState(false);
// I tried to put a onclick function, but not sure what to add here
const handleChange = () => {
axios
.get("https://api.com")
.then(res => {
const uri = res.data.faces[0].urls[4][512];
if (uri) {
setImage(uri);
} else {
console.log("nothing");
}
})
.catch(err => {
console.log(err.message);
});
};
return (
<div className="App">
<h1>Photo Generator</h1>
{image && <img src={image} alt="yourImage" />}
<button type="button" onClick={handleChange}>
Show new Image
</button>
</div>
);
}
You don't need to use useEffect Hook in this case.
also don't need to check setImage inside of API callback function.
You could do it like this
import React, {useState, useEffect} from 'react';
import './App.css';
import axios from 'axios';
function App() {
const [image, setImage] = useState('');
const [displayImage, setDisplayImage] = useState('none');
const handleChange = () => {
setDisplayImage('flex');
};
useEffect(() => {
axios
.get('https://api.com')
.then((res) => {
setImage
? setImage(res.data.faces[0].urls[4][512])
: console.log('nothing');
})
.catch((err) => {
console.log(err.message);
});
}, []);
return (
<div className="App">
<h1>Photo Generator</h1>
<div style={{display: displayImage}}>
<img style src={image} />
</div>
<button onClick={() => handleChange()}>Show new Image</button>
</div>
);
}
You could also do
<button onClick={() => setDisplayImage('flex')}>Show new Image</button>

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