I am building react app using express js, axios, and Mongo DB. When I navigate from the home page to the detailed page (by clicking overlay) , react renders the detailed page twice and after 2nd time page disappears in a flash. Here are the pages.
Home page
const Post = ({ post, setCurrentId }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = JSON.parse(localStorage.getItem("profile"));
const postDetails = (id) => {
navigate(`posts/${id}`);
};
const deletePostHandler = () => {
if (post.creator === user.id || post.creator === user.sub) {
dispatch(deletePost(post._id));
}
};
return (
<div className="post" key={post.id}>
<div className="user-details">
<img src={post.selectedFile} alt="" title={post.title} />
<div className="overlay" onClick={() => postDetails(post._id)}></div> // here is the link to detailed page passing id to retrive specific details
<div className="top-left">
<span className="user-name">{post.name}</span>
<span className="time">{moment(post.createdAt).fromNow()}</span>
</div>
<button className="edit-button" onClick={() => setCurrentId(post._id)}>
<MoreHorizIcon></MoreHorizIcon>
</button>
</div>
<div className="details">
<span className="tags">
{post.tags[0].split(",").map((tag) => "#" + tag.trim() + " ")}
</span>
<span className="title">{post.title}</span>
<span className="message">{post.message}</span>
</div>
<div className="button-container">
<button
className="like-button"
onClick={() => dispatch(likePost(post._id))}
>
<ThumbUpIcon className="thumbUpIcon"></ThumbUpIcon>
{/* <ThumbUpOffAltIcon></ThumbUpOffAltIcon> */}
<span>
{post.likes.length > 1
? `Likes ${post.likes.length}`
: `Like ${post.likes.length}`}
</span>
</button>
<button className="delete-button" onClick={deletePostHandler}>
<DeleteIcon></DeleteIcon>
</button>
</div>
</div>
);
};
export default Post;
Detailed page
const PostDetails = () => {
const dispatch = useDispatch();
const { id } = useParams();
useEffect(() => {
dispatch(getPost(id));
}, []);
const { post } = useSelector((state) => state.posts);
if (!post) return null;
return (
<div className="post-detail-container">
<div className="post-details">
<div className="title">{post.title}</div>
<div className="tags">
{post.tags[0].split(",").map((tag) => "#" + tag.trim() + " ")}
</div>
<p readOnly className="message">
{post.message}
</p>
<div className="createdBy">Created by: {post.name}</div>
<div className="createdBy">{moment(post.createdAt).fromNow()}</div>
</div>
<img
className="post-image"
src={post.selectedFile}
alt=""
title={post.title}
/>
</div>
);
};
postReducer
import { POST_ACTION_TYPES } from "../../action/post/postActionTypes";
export const postReducer = (state = [], action) => {
//state always will be posts
switch (action.type) {
case POST_ACTION_TYPES.FETCH_ALL:
return action.payload;
case POST_ACTION_TYPES.FETCH_POST:
return { ...state, post: action.payload };
case POST_ACTION_TYPES.CREATE:
return [...state, action.payload];
case POST_ACTION_TYPES.UPDATE:
case POST_ACTION_TYPES.LIKE:
return state.map((post) =>
post._id === action.payload._id ? action.payload : post
);
case POST_ACTION_TYPES.DELETE:
return state.filter((post) => post._id !== action.payload);
default:
return state;
}
};
Please explain why is that and help me to fix it.
Can you provide the logs?
Check the browser console for any errors, it may be that post is an array so you've to check if the array is empty by array.length < 1 ? return null : return (<Component/>) or you can use lodash _. isEmpty() . An empty array is a truthy value in javascript so if(!post) will return false
Related
not getting quantity
whenever i push a product inside a cart, the product has been duplicated,
in single product page, if i add a cart then "add to cart" should be change into "go to cart"
this is my cartSlice page.
const CartSlice = createSlice({
name: "cart",
initialState: [],
reducers: {
add(state, action) {
state.push(action.payload);
},
remove(state, action) {
return state.filter((item) => item.id !== action.payload);
},
},
});
this is my singleProduct page
const SingleProduct = () => {
const dispatch = useDispatch();
const { data: products } = useSelector((state) => state.product);
const { productId } = useParams();
const product = products.find((product) => String(product.id) === productId);
const handleAdd = (product) => {
dispatch(add(product));
};
return (
<section className={style.SingleProductSection}>
<div className={style.btns}>
<button
className={style.addToCart}
onClick={() => {
handleAdd(product);
}}
>
<FaCartPlus />
<span>Add to cart</span>
</button>
<Link to="/buyNow">
<button className={style.buyNow}>
<AiFillThunderbolt /> <span>Buy Now</span> {/* buy now page */}
</button>
</Link>
</div>
);
};
here is my cart page
const Cart = () => {
const [total, setTotal] = useState();
const dispatch = useDispatch();
const carts = useSelector((state) => state.cart);
const handleRemove = (productId) => {
return dispatch(remove(productId));
};
useEffect(() => {
setTotal(
carts.reduce(
(acc, curr) => acc + Number(curr.price.toString().split(".")[0]),
0
)
);
}, [carts]);
return (
<>
{carts.map((product) => {
return (
<div className={style.product_cart} quantity={product.quantity}>
<img
src={product.image}
alt="product_image"
className={style.product_image}
/>
<p className={style.product_title}>
{product.title.substring(0, 18)}...
</p>
<p className={style.product_price}>
₹{product.price.toString().split(".")[0]}
</p>
<div className={style.product_quantity}>
<button className="decrement">-</button>
<p>{/* {quantity} */}0</p>
<button className="increment">+</button>
</div>
<button
onClick={() => {
handleRemove(product.id);
}}
>
<AiFillDelete className={style.product_delete_icon} />
</button>
</div>
);
})}
</div>
<div className={style.cartItem_2}>
<p className={style.product_total}>
<span>Total :</span>
<strong>₹{total}</strong>
</p>
<div className={style.cart_buyNow}>
<Link to="/buyNow">
<button className={style.buyNow}>
<AiFillThunderbolt /> <span>Buy Now</span> {/* buy now page */}
</button>
</Link>
</div>
</>
);
};
not getting quantity
whenever i push a product inside a cart, the product has been duplicated,
in single product page, if i add a cart then "add to cart" should be change into "go to cart"
not able to solve this problem,
It's hard to be sure that these are the exact causes but hopefully these points will help a bit.
not getting quantity - try using product.quantity here instead of just quantity
<button className="decrement">-</button>
<p>{/* {quantity} */}0</p> // <-- change here
<button className="increment">+</button>
whenever i push a product inside a cart, the product has been duplicated - you don't want to mutate the store in redux, so try this (remove is fine):
add(state, action) {
return [...state, action.payload];
},
add to cart/go to cart - you could do something like use a piece of state to track if the user has clicked add, if they haven't, do one thing, if they have, do another:
const SingleProduct = () => {
const dispatch = useDispatch();
const { data: products } = useSelector((state) => state.product);
const { productId } = useParams();
const product = products.find((product) => String(product.id) === productId);
// addition
const [hasClickedAdd, setHasClickedAdd] = useState(false)
// addition
const handleAdd = (product) => {
dispatch(add(product));
// addition
setHasClickedAdd(true);
// addition
};
return (
<section className={style.SingleProductSection}>
<div className={style.btns}>
<button
className={style.addToCart}
onClick={() => {
handleAdd(product);
}}
>
<FaCartPlus />
// addition
<span>{hasClickedAdd ? 'Buy now' : 'Add to cart'}</span>
// addition
</button>
<Link to="/buyNow">
<button className={style.buyNow}>
<AiFillThunderbolt /> <span>Buy Now</span> {/* buy now page */}
</button>
</Link>
</div>
);
};
nb, if you do this, you're probably also going to want to change the click handler so that clicking on the 'Buy now' text doesn't just add another one to the cart
I have a list of dynamically generated items with information about real estate objects. Every item has a button. When clicked a contact form pops up. However when a button is clicked the event fires on all of the items in the list instead of only on that single item? I solved it with javascript event delegation but that's not the react-way to handle this. What is the best way to do this in react? I'm using React v18, React-Hooks and Redux-Toolkit. Data is fetched from a mongoDB database using Express.
Thanks you!
// Items list with pagination and map component
// Map component also contains the button!
const Content = () => {
const [show, setShow] = useState(false)
const [currentNumber, setCurrentNumber] = useState(1)
const [newList, setNewList] = useState([])
const [errorMessage, setErrorMessage] = useState(false)
const realestates = useSelector(state => state.realestate)
const { loading, realestate, error } = realestates
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchData())
if (realestate) {
setShow(true)
}
if (error) {
setErrorMessage(true)
}
}, [dispatch, error, realestate])
const pageNumbers = []
const resultsPerPage = 4
const pages = Math.ceil(realestate.length / resultsPerPage)
for (let i = 1; i <= pages; i++) {
pageNumbers.push(i)
}
const pagination = (number) => {
setCurrentNumber(number)
}
const slicedList = useCallback(() => {
const data2 = realestate.slice(((currentNumber - 1) * resultsPerPage), (currentNumber * resultsPerPage))
setNewList(data2)
}, [currentNumber, realestate])
useEffect(() => {
slicedList()
}, [slicedList])
return (
<div className="content2">
{errorMessage ? <div>{error}</div> : ""}
//List item
{show ? newList.map(item => {
const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;
return (
<div className="content2_property" key={_id}>
<div className="content2_castleImageBox"><img src={img} alt="" className="content2_castleImage" /></div>
<div className="content2_info">
<div className="title"><h5>{name}</h5></div>
<div className="location">
<div><span>Region:</span> {region}</div>
<div><span>Departement:</span> {departement}</div>
<div><span>City:</span> {city}</div>
</div>
<div className="icons">
<div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
<div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
<div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
<div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
</div>
<div className="price"><span>Price:</span> {item.price === 'Not for Sale' ? price : `$${price},-`}</div>
</div>
<Map region={region} map={map} />
</div>
)
}) : <Loader />}
// Pagination buttons
<div className="btns">
{pageNumbers.map((number, index) => {
return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
<button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
})}
</div>
</div>
)
}
export default Content
Map component with button
const Map = ({ region, map }) => {
const [showRegionName, setShowRegionName] = useState(false)
const handleMouseOver = () => {
setShowRegionName((prev) => !prev);
}
const makeEnquiry = () => {
//show contact form
}
return (
<div className="mapEnquiryBox">
<div className="map" onMouseEnter={handleMouseOver} onMouseLeave={handleMouseOver}>
<img src={map} alt="map" />
<div className={showRegionName ? "regionName" : "regionName hide"} >{region}</div>
</div>
<button className="enquiry" onClick={makeEnquiry}>Make enquiry</button>
</div>
)
}
export default Map
I think, the issue is with the key in the component.
This is how React differentiated between the components.
This is how I recently made my pagination:
Parent
{pageNumbersArray.map(pageNumber => (
<PaginationButton
key={pageNumber}
active={pageNumber === currentPage}
disabled={false}
onClick={() => handlePageChange(pageNumber)}
title={pageNumber}
/>
))}
Pagination Button
export default function PaginationButton({title, onClick, active, disabled}) {
return (
<button onClick={disabled ? null : onClick}>
<span>
{title}
</span>
</button>
);
}
This might come in handy for you.
Problem solved. I had to split the items list into separate components. One for the list container , one for the list items and one for every single list item. In the single item component I managed the state of that particular item, so when clicking a button the event only fires on that particular item.
Like so:
The list container
<div className="content2">
{errorMessage ? <div>{error}</div> : ""}
<ListItems newList={newList} show={show}/>
<div className="btns">
{pageNumbers.map((number, index) => {
return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
<button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
})}
</div>
</div>
The list items
const ListItems = ({ newList, show }) => {
return (
<>
{show ? newList.map(item => {
const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;
return (
<SingleProperty id={_id} area={area} bathrooms={bathrooms} bedrooms={bedrooms} city={city}
departement={departement} region={region} img={img} livingspace={livingspace} map={map} name={name} price={price}/>
)
}) : < Loader />}
</>
)
}
An the single item component
const SingleProperty = ({ id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price}) => {
const [ showForm, setShowForm ] = useState(false)
return (
<div className={!showForm ? "content2_property" : "content2_property enlarge"} key={id}>
<div className="content2_property_castleImageBox"><img src={img} alt="castle" className="content2_property_castleImage" /></div>
<div className="content2_property_info">
<div className="title"><h5>{name}</h5></div>
<div className="location">
<div><span>Region:</span> {region}</div>
<div><span>Departement:</span> {departement}</div>
<div><span>City:</span> {city}</div>
</div>
<div className="icons">
<div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
<div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
<div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
<div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
</div>
<div className="price"><span>Price:</span> {price === 'Not for Sale' ? price : `$${price},-`}</div>
</div>
<Map region={region} map={map} setShowForm={setShowForm}/>
</div>
)
}
I'm trying to create a simple Cart using Redux, but removeFromCart() function throws an error.
I have created a cartActions.js file with two dispatch functions, addToCart() function that works as it should and removeFromCart() function that returns TypeError: getState is not a function.
How can I fix this problem ?
This is the cartActions.js file:
import { ADD_TO_CART, REMOVE_FROM_CART } from "../types";
export const addToCart = (product) => (dispatch, getState) => {
const cartItems = getState().cart.cartItems.slice();
let alreadyExists = false;
cartItems.forEach((x) => {
if (x._id === product._id) {
alreadyExists = true;
x.count++;
}
});
if (!alreadyExists) {
cartItems.push({ ...product, count: 1 });
}
dispatch({
type: ADD_TO_CART,
payload: { cartItems },
});
localStorage.setItem("cartItems", JSON.stringify(cartItems));
console.log(cartItems)
};
export const removeFromCart = (product) => (dispatch, getState) => {
const cartItems = getState().cart.cartItems.slice()
.filter((x) => x._id !== product._id);
dispatch({
type: REMOVE_FROM_CART,
payload: { cartItems },
});
localStorage.setItem("cartItems", JSON.stringify(cartItems));
};
This is the error that I get:
24 |
25 | export const removeFromCart = (product) => (dispatch, getState) => {
> 26 | const cartItems = getState().cart.cartItems.slice()
27 | .filter((x) => x._id !== product._id);
28 | dispatch({
29 | type: REMOVE_FROM_CART,
The error occurs when the cart component is supposed to render.
I have a function openCart() that alternate between rendering the "Shop" and "Cart" when I press the button the error occurs
This is the openCart() function:
openCart = () => {
this.setState({openCartStatus:!this.state.openCartStatus});
} // the function starts with openCartStatus as false
The cart.js file is long but I think it’s necessary to include it for reference.
I used react hooks for cart.js.
this is the cart.js:
import React, { useRef } from 'react';
import './cart.css'
import { useIntersection } from 'react-use';
import formatCurrency from '../../../util'
import { Fade } from "react-awesome-reveal";
import { connect } from 'react-redux';
import { removeFromCart } from '../../../actions/cartActions';
function Cart (props) {
const sectionRef = useRef(null);
const intersection = useIntersection(sectionRef, {
root: null,
rootMargin: "80%",
threshold: 0.8,
});
const { cartItems } = props;
return (
<div className="container" >
<div className="cart_container" ref={sectionRef}>
<div className="cart_icon_container">
<div className={intersection && intersection.intersectionRatio < 0.5 ? "cart_Icon" : "cart_Icon_Btn"} onClick={() => {
props.openCart();
}}>
<img alt='cart-icon' src='./Images/icons8-shopping-bag-32.png' ></img>
</div>
</div>
{cartItems.length === 0 ? (
<div className="cart cart_header">
Cart Is Empty
</div>
) : (
<div className="cart cart_header">
You Have {cartItems.length} Itames In The Cart{""}
</div>
)}
<div>
<div className="cart">
<Fade direction="up" triggerOnce cascade duration="750">
<ul className="cart_items">
{cartItems.map((item , index ) => (
<li key={index}>
<div className="cart_image">
<img src={item.image} alt={item.name}></img>
</div>
<div className="ShopItem_details_discription">
<div className="productInfo_Container">
<div> LOGO </div>
<div className="productInfo">
<h2>{item.name}</h2>
<p>{item.info}</p>
</div>
<div >
<p> Phone Number</p>
<div className="flourType_Container" >
{item.flourType.map((x)=>(
<div>{" "}{x}</div>
))}
</div>
</div>
</div>
</div>
<div className="cart_mengment">
{formatCurrency(item.price)}
<div className="btn_containar">
<button onClick={() => props.addAmount(item)} className = "Btn" > + </button>
<div className = "ItemCounte"> {item.count}</div>
<button onClick={() => props.subAmount(item, index)} className = "Btn" > - </button>
</div>
<button className="cart_item_remove" onClick={() => props.removeFromCart(index)}>
Remove
</button>
</div>
</li>
))}
</ul>
</Fade>
</div>
</div>
{cartItems.length !== 0 && (
<div className="cart">
<div className="total">
<div >
TOTAL{" "}
{formatCurrency(
cartItems.reduce((a, c) => a + c.price * c.count, 0)
)}
</div>
<button className="proceed_Btn" onClick={() => {
props.showCheckout();
}}>
Proceed
</button>
</div>
</div>
)}
</div>
</div>
)
}
export default connect((state) => ({
cartItems: state.cart.cartItems,
}),
removeFromCart
)(Cart);
You need to pass object as mapDispatchToProps in connect method instead of just removeFromCard.
export default connect((state) => ({
cartItems: state.cart.cartItems,
}),
{ removeFromCart }
)(Cart);
Read: https://react-redux.js.org/using-react-redux/connect-mapdispatch
Your case scenario is essentially the first example. You don't need to pass your removeFromCart function to the component as it's already available due to your import.
If you remove the 2nd argument from connect, Redux's dispatch function is passed as a prop. So you'd need to change
export default connect((state) => ({
cartItems: state.cart.cartItems,
}),
removeFromCart
)(Cart);
to
export default connect((state) => ({
cartItems: state.cart.cartItems,
}))(Cart);
and
<button className="cart_item_remove" onClick={() => props.removeFromCart(index)}>Remove</button>
to
<button className="cart_item_remove" onClick={() => props.dispatch(removeFromCart(index))}>Remove</button>
and it should work as expected (at least the getState function should work).
Tip: Look at Redux lifecycle implementations, Redux is incredibly complex at first glance but after you simplify the implementation with standards...it's really easy. I'd look at mapStateToProps, mapDispatchToProps in the link above.
Example
Component.js
export function Component({ hello, setHello }) {
return <>
<p>{hello}</p>
<button onClick={() => setHello('Bye')}>
</>
}
container.js
import { connect } from 'react-redux';
import { Component } from './Component.js';
import { setHello } from './action.js';
const mapStateToProps = (state, ownProps) => {
return {
hello: state.hello
}
}
const mapDispatchToProps = (dispatch) => {
return {
setHello: (input) => {
dispatch(setHello(input));
}
}
}
connect(mapStateToProps, mapDispatchToProps)(Component);
I would like to ask for some help with this, i don't know if its normal or not.
Have This components, one is a container that fetch the data and the second one receive the data and display it in a div. Nothing fancy.
const ProjectContainer = () => { // component
const projects = useSelector((state) => state.projectReducer.projects);
const count = useSelector((state) => state.projectReducer.count);
const isDarkMode = useSelector((state) => state.themeReducer.isDarkMode);
const [isLoading, setIsLoading] = useState(false);
const limit = 5;
const dispatch = useDispatch();
useEffect(() => {
console.log("INSIDE USEFFECT");
if (projects.length > 0) return; // avoid fetching data if the state has data already
async function getData() {
setIsLoading(true);
try {
const projectsCollectionRef = db.collection("project-collection");
const projectsCountRef = db
.collection("aggregation")
.doc("project-collection");
console.log("FETCHING DATA");
const responseCount = await projectsCountRef.get();
const count = await responseCount.data().count;
//dispatch
dispatch({ type: "SET_PROJECTS_COUNT", payload: count });
const response = await projectsCollectionRef
.orderBy("createdAt")
.limit(limit)
.get();
let dataSend = [];
response.forEach((document) => {
dataSend.push({ ...document.data(), uid: document.id });
});
//dispatch
dispatch({ type: "SET_PROJECTS", payload: dataSend });
setIsLoading(false);
} catch (error) {
console.error(error);
}
}
getData();
}, [dispatch, projects.length]);
return (
<div className="container mx-auto text-center">
<div>
Proyectos
</div>
{isLoading && projects.length === 0 ? (
<div >
<div id="title">
<p>
Cargando....
</p>
</div>
</div>
) : (
<>
{projects.length === 0 ? (
<div >
<div id="title" >
<p>
No hay proyectos que mostrar....
</p>
</div>
</div>
) : (
<>
<div >
{projects.map((project, index) => {
return (
<Project data={project} index={index} key={project.uid} />
);
})}
</div>
{count !== projects.length && (
<button>
Cargar más
</button>
)}
</>
)}
</>
)}
</div>
);
};
export default ProjectContainer;
The component that shows the data is something like this
import React from "react";
import { useSelector } from "react-redux";
const Project = (props) => {
const { data, index } = props;
console.log({ index });
const isDarkMode = useSelector((state) => state.themeReducer.isDarkMode);
return (
<div>
<div id="image">
<div>
<img
src={data.imageURL}
alt=""
/>
</div>
</div>
<div id="textblock">
<h1 >
{data.name}
</h1>
<div id="description">
<span >{data.description}</span>
<div >
<p>
Tecnologías
</p>
{data.technologies.map((technology) => {
return (
<span key={technology}>
{technology}
</span>
);
})}
</div>
<div >
<div >
<span>
Api Utilizada:
</span>
</div>
<div >
<span>
{data.usedAPI}
</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default Project;
I mean, it works, it does its job, but I don't know if it's correct, in a more realistic company work it should work like this ?
I read that Strict mode can force to do some re renders, but i checked and don't have it.
At the end console looks like this ..
thanks in advance everyone :)
React will re-render once for each dispatch, even if multiple dispatch functions are called in the same cycle. This article has some good info on it:
https://medium.com/unsplash/react-redux-performance-considerations-when-dispatching-multiple-actions-5162047bf8a6
Fortunately, there is a solution: the batch function provided by Redux:
https://react-redux.js.org/api/batch
Just call both of your dispatch calls from within a batch, and you should see it only re-render once.
I have a PostList component with an array of posts objects. I am rendering this list of post using another pure functional component Post using Array.map() method. Post component has another component - LikeButton to like or unlike a post. Now I want to show a spinner during like or unlike on top of that LikeButton component. LikeButton Component looks something like this:
const LikeButton = (props) => {
const likeBtnClasses = [classes.LikeBtn];
const loggedInUserId = useSelector((state) => state.auth.user.id);
const isLoading = useSelector((state) => state.post.loading);
const isPostLiked = props.post.likes.find(
(like) => like.user === loggedInUserId
);
const [isLiked, setLike] = useState(isPostLiked ? true : false);
const token = useSelector((state) => state.auth.token);
const dispatch = useDispatch();
if (isLiked) {
likeBtnClasses.push(classes.Highlight);
}
const postLikeHandler = () => {
if (!isLiked) {
setLike(true);
dispatch(actions.likePost(props.post._id, token));
} else {
setLike(false);
dispatch(actions.unlikePost(props.post._id, token));
}
};
return isLoading ? (
<Spinner />
) : (
<button
className={likeBtnClasses.join(" ")}
onClick={() => postLikeHandler()}
>
<i class="far fa-thumbs-up"></i>
<small>{props.post.likes.length}</small>
</button>
);
};
Instead of showing the spinner to that single post, I am seeing it on all the posts.
My Post component looks like this:
const Post = (props) => {
return (
<div className={classes.Post}>
<div className={classes.Author}>
<img src={props.postData.avatar} alt="avatar" />
<div className={classes.AuthorDetails}>
<h3>{props.postData.name}</h3>
</div>
</div>
<div className={classes.PostText}>
<p>{props.postData.text}</p>
</div>
<hr />
<div className={classes.PostTools}>
<LikeButton post={props.postData} />
<div className={classes.PostBtn}>
<i class="far fa-comments"></i>
<small>3</small>
</div>
<div className={classes.PostBtn}>
<i class="fas fa-share"></i>
<small>2</small>
</div>
</div>
</div>
);
};
PostList component:
class PostList extends React.Component {
state = {
posts: [
{
text: "POST1",
user: "XYZ",
name: "XYZ",
id: "post1",
likes: [],
},
{
text: "POST2",
user: "johndoe#test.com",
name: "John Doe",
id: "post2",
likes: [],
},
],
};
componentDidMount() {
if (this.props.token) {
this.props.onFetchPosts(this.props.token);
this.props.onFetchUserAuthData(this.props.token);
}
}
render() {
let posts = null;
if (this.props.posts.length === 0) {
posts = this.state.posts.map((post) => {
return <Post key={post.id} postData={post} />;
});
} else {
posts = this.props.posts.map((post) => {
return <Post key={post._id} postData={post} />;
});
}
return (
<div>
<CreatePost />
{posts}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.auth.token,
posts: state.post.posts,
loading: state.post.loading,
error: state.post.err,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onFetchPosts: (token) => dispatch(actions.fetchPosts(token)),
onFetchUserAuthData: (token) => dispatch(actions.fetchUser(token)),
};
};
Please do some change in your to checking like/unlike is loading or not for the LikeButton.
const LikeButton = (props) => {
....
const [isButtonLoading, setButtonLoading] = useState(false);
...
return isButtonLoading ? (
<Spinner />
) : (
<button
className={likeBtnClasses.join(" ")}
onClick={() => postLikeHandler();setButtonLoading(true)}
>
<i class="far fa-thumbs-up"></i>
<small>{props.post.likes.length}</small>
</button>
);
};
Then on your dispatch callback need to set the isButtonLoading value to false.
const buttonCallback() {
// here we need to reset our flag
setButtonLoading(false);
}
const postLikeHandler = () => {
if (!isLiked) {
setLike(true);
// for this action you need to create third parameter called as callback so after response our buttonCallback will call
dispatch(actions.likePost(props.post._id, token, buttonCallback));
} else {
setLike(false);
// for this action you need to create third parameter called as callback so after response our buttonCallback will call
dispatch(actions.unlikePost(props.post._id, token, buttonCallback);
}
};
fore more details please check here.
Hope this will help you.