I have been facing an issue with my code that whenever I add something to a cart it's being added but when am changing the quantity it adds another product with the same id and the new quantity amount even tho i have made a unique id and that's not working as it should be working:
Example of what am saying:
https://imgur.com/a/T1y1wdM
cartReducer.js
const initialState = {
cartItems: [],
};
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload;
state.cartItems.push(item)
if (state.cartItems.length > 0) {
const existItem = state.cartItems.find(
(x) => x.id === item.id
);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.id === existItem.id ? item : x
),
};
}
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
break;
default:
return state;
}
};
CartScreen.js
import { useDispatch, useSelector } from "react-redux";
import Message from "../components/Message";
import { Link, useNavigate, useParams, useLocation } from "react-router-dom";
import {
Row,
Col,
ListGroup,
Image,
Form,
Button,
Card,
} from "react-bootstrap";
import { addToCart, editQty } from "../actions/cartActions";
const CartScreen = () => {
const productId = useParams();
const location = useLocation();
const qty = location.search ? Number(location.search.split("=")[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty));
}
}, [dispatch, productId, qty]);
const removeFromCartHandler = (id) => {
console.log("rmeoved")
}
return (
<Row>
<Col md={8}>
<h1>Shopping Cart</h1>
{cartItems.length === 0 ? (
<Message>
Your cart is empty <Link to="/">Go back</Link>{" "}
</Message>
) : (
<ListGroup variant="flush">
{cartItems.map((item, index) => (
<ListGroup.Item key={index+1}>
<Row>
<Col md={2}>
<Image
src={item.image}
alt={item.name}
fluid
rounded
></Image>
</Col>
<Col md={3}>
<Link to={`/product/${item.id}`}>{item.name}</Link>
</Col>
<Col md={3}>
${item.price}
</Col>
<Col md={2}>
<Form.Control
as="select"
value={item.qty}
onChange={(e) => {
dispatch(addToCart(item, Number(e.target.value)));
}}
>
{[...Array(item.countInStock).keys()].map((x) => (
<option key={x + 1} value={x + 1}>
{x + 1}
</option>
))}
</Form.Control>
</Col>
<Col md={2}>
<Button type="button" variant="light" onClick={()=>{
removeFromCartHandler(item.product)
}}><i className="fa fa-trash"></i></Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</Col>
<Col md={2}></Col>
<Col md={2}></Col>
</Row>
);
};
export default CartScreen;
cartActions.js
import { CART_ADD_ITEM, CART_FAIL_ITEM, CART_UPDATE_ITEM } from "../constants/cartConstants";
import { useParams, useLocation } from "react-router-dom";
export const addToCart = (id, qty) => async (dispatch, getState) => {
try {
const url = `/api/products/${id.id}`;
const { data } = await axios.get(url);
dispatch({
type: CART_ADD_ITEM,
payload: {
id: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
});
} catch (error) {
dispatch({
type: CART_FAIL_ITEM,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
}; ```
Related
I'm creating an e-commerce website and I've run into an issue that I'm having trouble solving. The functionality to add products to the cart, route to CartScreen, and be able to increase/decrease quantity and remove items from the cart all work. However, if there is an item in the cart and I want to add another, instead of having both items in the cart it replaces the first item with the second and copies its quantity. Does anyone have any idea how I can fix this?
Here is my Product.js:
import { useEffect } from "react";
import Card from 'react-bootstrap/Card';
import Button from 'react-bootstrap/Button';
import { Link } from 'react-router-dom';
import Rating from './Rating';
import axios from 'axios';
import { useContext } from 'react';
import { Store } from '../Store';
function Product(props) {
const { product } = props;
const { state, dispatch: ctxDispatch } = useContext(Store);
const {
cart: { cartItems },
} = state;
const addToCartHandler = async (item) => {
const existItem = cartItems.find((x) => x._id === product._id);
const quantity = existItem ? existItem.quantity + 1 : 1;
const { data } = await axios.get(`/api/products/${item._id}`);
if (data.countInStock < quantity) {
window.alert('Sorry. Product is out of stock');
return;
}
ctxDispatch({
type: 'CART_ADD_ITEM',
payload: { ...item, quantity },
});
};
return (
<Card>
<Link to={`/product/${product.slug}`}>
<img src={product.image} className="card-img-top" alt={product.name} />
</Link>
<Card.Body>
<Link to={`/product/${product.slug}`}>
<Card.Title>{product.name}</Card.Title>
</Link>
<Rating rating={product.rating} numReviews={product.numReviews} />
<Card.Text>${product.price}</Card.Text>
{product.countInStock === 0 ? (
<Button variant="light" disabled>
Out of stock
</Button>
) : (
<Button onClick={() => addToCartHandler(product)}>Add to cart</Button>
)}
</Card.Body>
</Card>
);
}
export default Product;
Here is my CartScreen.js:
import { useContext } from 'react';
import { Store } from '../Store';
import { Helmet } from 'react-helmet-async';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import MessageBox from '../components/MessageBox';
import ListGroup from 'react-bootstrap/ListGroup';
import Button from 'react-bootstrap/Button';
import Card from 'react-bootstrap/Card';
import { Link, useNavigate } from 'react-router-dom';
import axios from 'axios';
export default function CartScreen() {
const navigate = useNavigate();
const { state, dispatch: ctxDispatch } = useContext(Store);
const {
cart: { cartItems },
} = state;
const updateCartHandler = async (item, quantity) => {
const { data } = await axios.get(`/api/products/${item._id}`);
if (data.countInStock < quantity) {
window.alert('Sorry. Product is out of stock');
return;
}
ctxDispatch({
type: 'CART_ADD_ITEM',
payload: { ...item, quantity },
});
};
const removeItemHandler = (item) => {
ctxDispatch({ type: 'CART_REMOVE_ITEM', payload: item });
};
const checkoutHandler = () => {
navigate('/signin?redirect=/shipping');
};
return (
<div>
<Helmet>
<title>Shopping Cart</title>
</Helmet>
<h1>Shopping Cart</h1>
<Row>
<Col md={8}>
{cartItems.length === 0 ? (
<MessageBox>
Cart is empty. <Link to="/">Go Shopping</Link>
</MessageBox>
) : (
<ListGroup>
{cartItems.map((item, i) => (
<ListGroup.Item key={i}>
<Row className="align-items-center">
<Col md={4}>
<img
src={item.image}
alt={item.name}
className="img-fluid rounded img-thumbnail"
></img>{' '}
<Link to={`/product/${item.slug}`}>{item.name}</Link>
</Col>
<Col md={3}>
<Button
onClick={() =>
updateCartHandler(item, item.quantity - 1)
}
variant="light"
disabled={item.quantity === 1}
>
<i className="fas fa-minus-circle"></i>
</Button>{' '}
<span>{item.quantity}</span>{' '}
<Button
variant="light"
onClick={() =>
updateCartHandler(item, item.quantity + 1)
}
disabled={item.quantity === item.countInStock}
>
<i className="fas fa-plus-circle"></i>
</Button>
</Col>
<Col md={3}>${item.price}</Col>
<Col md={2}>
<Button
onClick={() => removeItemHandler(item)}
variant="light"
>
<i className="fas fa-trash"></i>
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</Col>
<Col md={4}>
<Card>
<Card.Body>
<ListGroup variant="flush">
<ListGroup.Item>
<h3>
Subtotal ({cartItems.reduce((a, c) => a + c.quantity, 0)}{' '}
items) : $
{cartItems.reduce((a, c) => a + c.price * c.quantity, 0)}
</h3>
</ListGroup.Item>
<ListGroup.Item>
<div className="d-grid">
<Button
type="button"
variant="primary"
onClick={checkoutHandler}
disabled={cartItems.length === 0}
>
Proceed to Checkout
</Button>
</div>
</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>
</Col>
</Row>
</div>
);
}
This is my Store.js:
import { createContext, useReducer } from 'react';
export const Store = createContext();
const initialState = {
cart: {
cartItems: localStorage.getItem('cartItems')
? JSON.parse(localStorage.getItem('cartItems'))
: [],
},
};
function reducer(state, action) {
switch (action.type) {
case 'CART_ADD_ITEM':
// add to cart
const newItem = action.payload;
const existItem = state.cart.cartItems.find(
(item) => item._id === newItem._id
);
const cartItems = existItem
? state.cart.cartItems.map((item) =>
item._id === existItem._id ? newItem : item
)
: [...state.cart.cartItems, newItem];
localStorage.setItem('cartItems', JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
case 'CART_REMOVE_ITEM': {
const cartItems = state.cart.cartItems.filter(
(item) => item._id !== action.payload._id
);
localStorage.setItem('cartItems', JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
}
default:
return state;
}
}
export function StoreProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch };
return <Store.Provider value={value}>{props.children} </Store.Provider>;
}
Before in the CartScreen I was getting an Error of child in the list should be a unique key prop. But after fixing the issue still remains so I guess that wasn't the issue.
i need to ask some question
I have 1 parent(index.js) and 2 childs (JobRequirement.js and JobList.js)
how can i send a data from job requirement to joblist?
here is my code
// JOBREQUIREMENT.JS
const JobRequirement = ({ isOpen, handleSelect }) => {
const [datas, setDatas] = useState([])
const loadData = () => {
axios.get('/bla')
.then((res) => {
setDatas(res.data.data)
})
}
useEffect(() => {
loadData()
}, [])
if (isOpen) {
return (
<Fragment>
<h5 className='mt-3 mb-2'>Job Requirements</h5>
<Row>
{datas.map((data) => (
<Col md='6' lg='4' key={data.id}>
<Card onClick={() => handleSelect(data.id)} className='text-center vertical-align-center mb-3'>
<CardBody>
<CardBody tag='h4'>{data.name}</CardBody>
</CardBody>
</Card>
</Col>
))}
</Row>
</Fragment>
)
} else return (<></>)
}
export default JobRequirement
// INDEX.JS
import { Fragment, useState } from 'react'
import JobRequirement from './JobRequirement'
import JobList from './JobList'
export default function JobRequirements() {
const [jobRequirement, setJobRequirement] = useState(true)
const [jobList, setJobList] = useState(false)
const selectJobRequirement = (data) => {
setJobRequirement(false)
setJobList(true, data)
}
const closeJobList = () => {
setJobRequirement(true)
setJobList(false)
}
return (
<div>
<Fragment>
<JobRequirement isOpen={jobRequirement} handleSelect={selectJobRequirement} />
<JobList isOpen={jobList} close={closeJobList} />
</Fragment>
</div>
)
}
// JOBLIST.JS
import { Fragment } from 'react'
import { Button, Row, Col, Card, CardBody } from 'reactstrap'
import { ChevronLeft } from 'react-feather'
const JobList = ({ isOpen, close, data }) => {
console.log(data)
if (isOpen) {
return (
<Fragment>
<div className='mt-2 mb-2'>
<Button size="sm" className='btn-icon' onClick={() => close()}><ChevronLeft size='15'/></Button> List of Job
</div>
<div>
<Row>
<Col md='6' lg='4'>
<Card className='text-center vertical-align-center mb-3'>
<CardBody>
<CardBody tag='h4'>Hello</CardBody>
</CardBody>
</Card>
</Col>
</Row>
</div>
</Fragment>
)
} else return (<></>)
}
export default JobList
I need to get a {data.id} from jobrequirement.js and use it in joblist.js
thankyou so much
i am tying to create ecommerce website using mern. for the homepage all products are showing but whenever i click on single product the product page is not loading . Cannot read properties of undefined (reading 'params'),
HomeScreen.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { Row, Col } from 'react-bootstrap';
import Product from '../components/Product';
import { listProducts } from '../actions/productActions';
import Loader from '../components/Loader'
import Message from '../components/Message'
const HomeScreen = () => {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const { loading, error, products } = productList
useEffect(() => {
dispatch(listProducts())
}, [dispatch])
return <>
<h1 className='text-center my-3'> Latest Products</h1>
{loading ? (
<Loader />
) : error ? (
<Message variant='danger'> {error} </Message>
) : (
<Row>
{products.map((product) => (
<Col key={product._id} sm={12} md={6} lg={4} xl={3}>
<Product product={product} />
</Col>
))}
</Row>
)}
</>;
};
export default HomeScreen;
ProductScreen.js
import React, { useEffect } from 'react'
import { Link } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import Rating from '../components/Rating'
import Message from '../components/Message'
import Loader from '../components/Loader'
import { listProductDetails } from '../actions/productActions'
const ProductScreen = ({ match }) => {
const dispatch = useDispatch()
const productDetails = useSelector((state) => state.productDetails)
const { loading, error, product,id } = productDetails
useEffect(() => {
dispatch(listProductDetails(match.params.id))
}, [dispatch, match, id])
return (
<>
<Link className='btn btn-light my-3' to='/'>
Go Back
</Link>
{loading ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : (
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} fluid />
</Col>
<Col md={3}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating
value={product.rating}
text={`${product.numReviews} reviews`}
/>
</ListGroup.Item>
<ListGroup.Item>Price: ${product.price}</ListGroup.Item>
<ListGroup.Item>
Description: {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup variant='flush'>
<ListGroup.Item>
<Row>
<Col>Price:</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Status:</Col>
<Col>
{product.countInStock > 0 ? 'In Stock' : 'Out Of Stock'}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Button
className='btn-block'
type='button'
disabled={product.countInStock === 0}
>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
)}
</>
)
}
export default ProductScreen
App.js
import { BrowserRouter as Router, Route, Routes} from "react-router-dom";
import React from "react";
import Footer from "./components/Footer";
import Header from "./components/Header";
import { Container } from "react-bootstrap";
import HomeScreen from "./screens/HomeScreen";
import ProductScreen from "./screens/ProductScreen"
function App() {
return (
<Router>
<Header />
<main className="py-3">
<Container>
<Routes>
<Route path="/" element={ <HomeScreen />} exact />
<Route path="/product/:id" element={ <ProductScreen />} />
</Routes>
</Container>
</main>
<Footer />
</Router>
);
}
export default App;
productActions.js
import axios from 'axios'
import {
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from '../constants/productConstants'
export const listProducts = () => async (dispatch) => {
try {
dispatch({ type: PRODUCT_LIST_REQUEST })
const { data } = await axios.get('/api/products')
dispatch({
type: PRODUCT_LIST_SUCCESS,
payload: data,
})
} catch (error) {
dispatch({
type: PRODUCT_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
export const listProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST })
const { data } = await axios.get(`/api/products/${id}`)
dispatch({
type: PRODUCT_DETAILS_SUCCESS,
payload: data,
})
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
}
productConstants.js
export const PRODUCT_LIST_REQUEST = 'PRODUCT_LIST_REQUEST'
export const PRODUCT_LIST_SUCCESS = 'PRODUCT_LIST_SUCCESS'
export const PRODUCT_LIST_FAIL = 'PRODUCT_LIST_FAIL'
export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST";
export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS";
export const PRODUCT_DETAILS_FAIL = "PRODUCT_DETAILS_FAIL";
productReducers.js
import {
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from '../constants/productConstants'
export const productListReducer = (state = { products: [] }, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return { loading: true, products: [] }
case PRODUCT_LIST_SUCCESS:
return { loading: false, products: action.payload }
case PRODUCT_LIST_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
export const productDetailsReducer = (
state = { product: { reviews: [] } },
action
) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return { loading: true, ...state }
case PRODUCT_DETAILS_SUCCESS:
return { loading: false, product: action.payload }
case PRODUCT_DETAILS_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
server.js
import express from 'express'
import dotenv from 'dotenv'
import { notFound, errorHandler } from './middleware/errorMiddleware.js'
import connectDB from './config/db.js'
import productRoutes from './routes/productRoutes.js'
dotenv.config()
connectDB()
const app = express();
app.get('/', (req, res) => {
res.send('Hello from the root application URL...')
});
app.use('/api/products', productRoutes)
app.use( notFound )
app.use( errorHandler)
const PORT = process.env.PORT || 5000
app.listen(PORT, console.log( `server is running IN ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold))
You are trying to read a property of a variable in ProductScreen.js which is not initialised by you. Very likely that is match.params.id.
I've been having an issue with react not rendering the updated cartItems in the state. The state updates perfectly fine but the items that I delete don't actually get removed from the rendering.
I'm very new to react, redux and state management but I assume nothing's actually wrong with the state itself, rather how/when the items are being iterated to be rendered. It's only when I refresh the page that it renders accurately.
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { Row, Col, ListGroup, Image, Button } from "react-bootstrap";
import Message from "../components/Message";
import { addToCart, removeFromCart } from "../actions/cartActions";
function Basket({ match, location, history }) {
const productId = match.params.id;
const qty = location.search ? Number(location.search.split("?")[1]) : 1;
const specID = location.search ? Number(location.search.split("?")[2]) : 0;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty, specID));
}
}, [dispatch, productId, qty, specID]);
const removeFromCartHandler = (cartItem) => {
dispatch(removeFromCart(cartItem));
};
const checkoutHandler = () => {
history.push("/login?redirect=shipping");
};
return (
<Row>
<Col md={8}>
{cartItems.length === 0 ? (
<Message variant="primary">
Your cart is empty{" "}
<Link to="/">
<strong>Go Back</strong>
</Link>
</Message>
) : (
<div>
<Link to="/">
<strong>Go Back</strong>
</Link>
<ListGroup variant="flush">
{cartItems.map((item) => (
<ListGroup.Item key={item.product} className="cart">
<Row>
{/* <Col xs={4} md={2}>
<Image src={item.img} alt={item.name} fluid rounded />
</Col> */}
<Col xs={4} md={3}>
<Link to={`/product/${item.product}`}>{item.name}</Link> (
{item.size})
</Col>
<Col xs={2} md={2}>
£{item.price}
</Col>
<Col xs={6} md={3}>
Qty
</Col>
<Col xs={6} md={1}>
<Button
type="button"
variant="light"
onClick={() => removeFromCartHandler(item)}
>
<i className="fas fa-trash" />
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
</div>
)}
</Col>
</Row>
);
}
export default Basket;
The reducer for the cart.
import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const cartReducer = (
state = { cartItems: [{ specList: [{ price: [] }] }] },
action
) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload;
const existItem = state.cartItems.find(
(x) => x.product === item.product && x.size === item.size
);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x
),
};
} else {
return { ...state, cartItems: [...state.cartItems, item] };
}
case CART_REMOVE_ITEM:
return {
...state,
cartItems: state.cartItems.filter((x) => x !== action.payload),
};
default:
return state;
}
};
import axios from "axios";
import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const addToCart = (id, qty) => async (dispatch, getState) => {
const { data } = await axios.get(`/api/products/${id}`);
dispatch({
type: CART_ADD_ITEM,
payload: {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
});
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
};
export const removeFromCart = (id) => async (dispatch, getState) => {
dispatch({
type: CART_REMOVE_ITEM,
payload: id,
});
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
};
You are calling () => removeFromCartHandler(item) but at any other point assume the payload is id. You probably want to do () => removeFromCartHandler(item.id).
Upon the click of a single like, it is increasing the number of likes for both separate components. What is causing both like numbers to increase, and how can I code it to where only one like number increases upon clicking a like?
I have also include the console in the picture below where I have console logged the logic in my reducer. You can find the code for the reducer further below the picture.
Reducer code
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "../actions/types";
const initialState = {
goals: [],
goal: null,
loading: true,
error: {}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_GOALS:
return {
...state,
goals: payload,
loading: false
};
case GOAL_ERROR:
return {
...state,
error: payload,
loading: false
};
case UPDATE_LIKES:
return {
...state,
goals: state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) ? { ...goal, likes: payload.likes } : goal
),
loading: false
};
default:
return state;
}
}
Action code
import axios from "axios";
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "./types";
// Get goals
export const getGoals = () => async dispatch => {
try {
const res = await axios.get("/api/goal/goalfeed");
dispatch({
type: GET_GOALS,
payload: res.data
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Add like
export const addLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/like/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Remove like
export const removeLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/unlike/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
Goals component code
import React, { useEffect } from "react";
import Moment from "react-moment";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { addLike, removeLike } from "../../actions/goal";
import { getGoals } from "../../actions/goal";
import Spinner from "../layout/Spinner";
import Navbar from "../dashboard/Navbar";
import ThumbUpAltIcon from "#material-ui/icons/ThumbUpAlt";
import ThumbDownAltIcon from "#material-ui/icons/ThumbDownAlt";
import ChatIcon from "#material-ui/icons/Chat";
import DeleteIcon from "#material-ui/icons/Delete";
import DoneIcon from "#material-ui/icons/Done";
import {
Typography,
Container,
CssBaseline,
makeStyles,
Grid,
Avatar,
Paper,
Button
} from "#material-ui/core";
const useStyles = makeStyles(theme => ({
paper: {
height: "auto",
marginBottom: theme.spacing(3)
},
actionButtons: {
marginTop: "3vh"
},
profileHeader: {
textAlign: "center",
marginBottom: 20
},
avatar: {
width: theme.spacing(7),
height: theme.spacing(7)
}
}));
const Goals = ({
getGoals,
auth,
addLike,
removeLike,
goal: { goals, user, loading }
}) => {
useEffect(() => {
getGoals();
}, [getGoals]);
const classes = useStyles();
return loading ? (
<>
<Navbar />
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Spinner />
</div>
</Container>
</>
) : (
<>
<CssBaseline />
<Navbar />
<main>
<Container>
<Typography variant="h2" className={classes.profileHeader}>
Goals
</Typography>
{/* parent grid */}
<Grid container spacing={4}>
{goals.map(singleGoal => (
<Grid
className={classes.paper}
key={singleGoal._id}
spacing={1}
container
item
direction="row"
alignItems="center"
component={Paper}
>
<Grid
item
container
direction="column"
justify="center"
alignItems="center"
xs={3}
>
<Avatar className={classes.avatar} src={singleGoal.avatar} />
<Typography variant="caption">
{singleGoal.first_name} {singleGoal.last_name}
</Typography>
<Typography variant="caption" className={classes.postedOn}>
Posted on{" "}
<Moment format="MM/DD/YYYY">{singleGoal.date}</Moment>
</Typography>
</Grid>
<Grid container item direction="column" xs={9}>
<Typography variant="body1">{singleGoal.text}</Typography>
<Grid item className={classes.actionButtons}>
<Button size="small" onClick={e => addLike(singleGoal._id)}>
<ThumbUpAltIcon />
</Button>
<Typography variant="caption">
{singleGoal.likes.length}
</Typography>
<Button
size="small"
onClick={e => removeLike(singleGoal._id)}
>
<ThumbDownAltIcon />
</Button>
<Button href={`/goal/${singleGoal._id}`} size="small">
<ChatIcon />
</Button>
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DoneIcon />
</Button>
)}
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DeleteIcon />
</Button>
)}
</Grid>
</Grid>
</Grid>
))}
</Grid>
</Container>
</main>
</>
);
};
Goals.propTypes = {
getGoals: PropTypes.func.isRequired,
goal: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
goal: state.goal,
auth: state.auth
});
export default connect(mapStateToProps, { getGoals, addLike, removeLike })(
Goals
);
There exists a flaw in your conditional test.
state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) // What is this? it will always evaluate to true
? { ...goal, likes: payload.likes }
: goal
)
console.log('EQUAL?', console.log() === console.log()); // true
console.log('EQUAL?', console.log(3) === console.log(3)); // true
console.log('EQUAL?', console.log(3) === console.log('three')); // true
console.log('EQUAL?', console.log('apple') === console.log({})); // true
console.log('EQUAL?', console.log(42) === console.log(-42)); // true
The function console.log is a void return, i.e. undefined, so you are comparing undefined === undefined, which is always true.
console.log(undefined === undefined); // true
You are spreading in the new 'likes' value to every goal object.
Try instead:
state.goals.map(
goal => goal._id === payload.goalId
? { ...goal, likes: payload.likes }
: goal
)