I've got this issue which I do not understand. I am getting an invariant error that says a state mutation has occurred, but at a very awkward place.
Basically I've got this page:
import React, { useState, useEffect } from "react";
import {
Link,
useLocation,
useNavigate,
useSearchParams,
} from "react-router-dom";
import { Form, Button, Row, Col, Table } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { useDispatch, useSelector } from "react-redux";
import Loader from "../components/Loader";
import Message from "../components/Message";
import FormContainer from "../components/FormContainer";
import { register } from "../actions/userActions";
import { getUserDetails, updateUserProfile } from "../actions/userActions";
import { USER_UPDATE_PROFILE_RESET } from "../constants/userConstants";
import { listMyOrders } from "../actions/orderActions";
function ProfileScreen() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [message, setMessage] = useState("");
const dispatch = useDispatch();
const location = useLocation();
const userDetails = useSelector((state) => state.userDetails);
const navigate = useNavigate();
const { error, loading, user } = userDetails;
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
const userUpdateProfile = useSelector((state) => state.userUpdateProfile);
const { success } = userUpdateProfile;
const orderListMy = useSelector((state) => state.orderListMy);
const { loading: loadingOrders, error: errorOrders, orders } = orderListMy;
useEffect(() => {
if (!userInfo) {
navigate("/login");
} else {
if (!user || !user.name || success) {
dispatch({ type: USER_UPDATE_PROFILE_RESET });
dispatch(getUserDetails("profile"));
dispatch(listMyOrders());
} else {
setName(user.name);
setEmail(user.email);
}
}
}, [dispatch, location, userInfo, user, success]);
const submitHandler = (e) => {
e.preventDefault();
if (password != confirmPassword) {
setMessage("Passwords do not match!");
} else {
dispatch(
updateUserProfile({
id: user._id,
name: name,
email: email,
password: password,
})
);
}
};
return (
<Row>
<Col md={3}>
<h2>User Profile</h2>
{message && <Message variant="danger">{message}</Message>}
{error && <Message variant="danger">{error}</Message>}
{loading && <Loader></Loader>}
<Form onSubmit={submitHandler}>
<Form.Group controlId="name">
<Form.Label>Name</Form.Label>
<Form.Control
required
type="name"
placeholder="Enter Name"
value={name}
onChange={(e) => setName(e.target.value)}
></Form.Control>
</Form.Group>
<Form.Group controlId="email">
<Form.Label>Email Address</Form.Label>
<Form.Control
required
type="email"
placeholder="Enter Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
></Form.Control>
</Form.Group>
<Form.Group controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Enter Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
></Form.Control>
</Form.Group>
<Form.Group controlId="passwordConfirm">
<Form.Label>Confirm Password</Form.Label>
<Form.Control
type="password"
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
></Form.Control>
</Form.Group>
<Button type="submit" variant="primary">
Update
</Button>
</Form>
</Col>
<Col md={9}>
<h2>My Orders</h2>
{loadingOrders ? (
<Loader />
) : errorOrders ? (
<Message variant="danger">{errorOrders}</Message>
) : (
<Table striped responsive className="table-sm">
<thead>
<tr>
<th>ID</th>
<th>Date</th>
<th>Total</th>
<th>Paid</th>
<th>Delivered</th>
<th></th>
</tr>
</thead>
<tbody>
{orders.map((order) => (
<tr key={order._id}>
<td>{order._id}</td>
<td>{order.createdAt.substring(0, 10)}</td>
<td>${order.totalPrice}</td>
<td>
{order.isPaid ? (
order.paidAt.substring(0, 10)
) : (
<i className="fas fa-times" style={{ color: "red" }}></i>
)}
</td>
<td>
<LinkContainer to={`/order/${order._id}`}>
<Button className="btn-sm">Details</Button>
</LinkContainer>
</td>
</tr>
))}
</tbody>
</Table>
)}
</Col>
</Row>
);
}
export default ProfileScreen;
and in this page whenever you click on the Details button on some of the orders you get redirected to this page:
import React, { useState, useEffect } from "react";
import { Button, Row, Col, ListGroup, Image, Card } from "react-bootstrap";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { PayPalButton } from "react-paypal-button-v2";
import Message from "../components/Message";
import Loader from "../components/Loader";
import CheckoutSteps from "../components/CheckoutSteps";
import {
createOrder,
getOrderDetails,
payOrder,
} from "../actions/orderActions";
import {
ORDER_CREATE_RESET,
ORDER_PAY_RESET,
} from "../constants/orderConstants";
function OrderScreen() {
const orderDetails = useSelector((state) => state.orderDetails);
const { order, error, loading } = orderDetails;
const orderPay = useSelector((state) => state.orderPay);
const { loading: loadingPay, success: successPay } = orderPay;
const dispatch = useDispatch();
const history = useNavigate();
const orderId = useParams();
const takenId = orderId.id;
const [sdkReady, setSdkReady] = useState(false);
const cart = useSelector((state) => state.cart);
let copyCart = Object.assign({}, cart);
if (!loading && !error) {
order.itemsPrice = order.orderItems
.reduce((acc, item) => acc + item.price * item.qty, 0)
.toFixed(2);
}
const addPayPalScript = () => {
const script = document.createElement("script");
script.type = "text/javascript";
script.src =
"https://www.paypal.com/sdk/js?client-id=ATMiiNcpLBRgha0mQCRpS5GJRC9YrATzPxyLFvWGoVsCvoxg46E0UGielBSPptskQWiqk8mrp2eQgpnC";
script.async = true;
script.onload = () => {
setSdkReady(true);
};
document.body.appendChild(script);
};
useEffect(() => {
if (!order || successPay || order._id !== Number(takenId)) {
dispatch({ type: ORDER_PAY_RESET });
dispatch(getOrderDetails(takenId));
} else if (!order.isPaid) {
if (!window.paypal) {
addPayPalScript();
} else {
setSdkReady(true);
}
}
}, [dispatch, order, orderId, successPay]);
const successPaymentHandler = (paymentResult) => {
dispatch(payOrder(takenId, paymentResult));
};
return loading ? (
<Loader />
) : error ? (
<Message variant="danger">{error}</Message>
) : (
<div>
<h1>Order: {order._id}</h1>
<Row>
<Col md={8}>
<ListGroup variant="flush">
<ListGroup.Item>
<h2>Shipping</h2>
<p>
<strong>Name: </strong>
{order.user.name}
</p>
<p>
<strong>Email: </strong>
<a href={`mailto:${order.user.email}`}>{order.user.email}</a>
</p>
<p>
<strong>Shipping: </strong>
{order.shippingAddress.address}, {order.shippingAddress.city}
{" "}
{order.shippingAddress.postalCode},{" "}
{order.shippingAddress.country}
</p>
{order.isDelivered ? (
<Message variant="success">
Delivered on {order.deliveredAt}
</Message>
) : (
<Message variant="warning">Not Delivered</Message>
)}
</ListGroup.Item>
<ListGroup.Item>
<h2>Payment Method</h2>
<p>
<strong>Method: </strong>
{order.paymentMethod}
</p>
{order.isPaid ? (
<Message variant="success">Paid on {order.paidAt}</Message>
) : (
<Message variant="warning">Not Paid</Message>
)}
</ListGroup.Item>
<ListGroup.Item>
<h2>Order Items</h2>
{order.orderItems.length === 0 ? (
<Message variant="info">Your order is empty</Message>
) : (
<ListGroup variant="flush">
{order.orderItems.map((item, index) => (
<ListGroup.Item key={index}>
<Row>
<Col md={1}>
<Image
src={item.image}
alt={item.name}
fluid
rounded
/>
</Col>
<Col>
<Link to={`/product/${item.product}`}>
{item.name}
</Link>
</Col>
<Col md={4}>
{item.qty} X ${item.price} = $
{(item.qty * item.price).toFixed(2)}
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={4}>
<Card>
<ListGroup variant="flush">
<ListGroup.Item>
<h2>Order Summary</h2>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Items:</Col>
<Col>${order.itemsPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping:</Col>
<Col>${order.shippingPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Tax:</Col>
<Col>${order.taxPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Total:</Col>
<Col>${order.totalPrice}</Col>
</Row>
</ListGroup.Item>
{!order.isPaid && (
<ListGroup.Item>
{loadingPay && <Loader />}
{!sdkReady ? (
<Loader />
) : (
<PayPalButton
amount={order.totalPrice}
onSuccess={successPaymentHandler}
/>
)}
</ListGroup.Item>
)}
</ListGroup>
</Card>
</Col>
</Row>
</div>
);
}
export default OrderScreen;
Here everything works perfectly fine, but when you decide to go from this page to the Home page:
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Row, Col } from "react-bootstrap";
import Product from "../components/Product";
import Loader from "../components/Loader";
import Message from "../components/Message";
import { listProducts } from "../actions/productActions";
function HomeScreen() {
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { error, loading, products } = productList;
useEffect(() => {
dispatch(listProducts());
}, [dispatch]);
return (
<div>
<h1>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>
)}
</div>
);
}
export default HomeScreen;
You get the Invariant failed error at line 13, where I'm trying to dispatch the listProducts method.
I've had a situation with this error not so long ago, managed to fix it because I used to update a state directly. I understand why I used to get that error before, but this time I am way more confused than before, because this time I am literally not touching any value anywhere along the state, I am just displaying it. I've checked the backend and I get no errors there, meaning this is strictly a React problem. I do not understand why the problem is occurring only when I am trying to go from details to the home screen.
Related
Unable to add product into the cart although, the url is getting changed, it showing two errors in the console, cant understand what is wrong.
http://localhost:3000/cart/636d4264ff92e60977f3bb4a?qt=3
636d4264ff92e60977f3bb4a = productID and qt=3 quantity
enter image description here
here are my files
import React, { useEffect } from 'react'
import { Link, useParams, useLocation, useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { Row, Col, ListGroup, Image, Form, Button, Card } from 'react-bootstrap'
import Message from '../components/Message'
import { addToCart, removeFromCart } from '../actions/cartActions'
const CartScreen = ({match, history}) => {
const {id} = useParams();
//const productId = match.params.id
const location = useLocation();
const qty = location.search ? Number (location.search.split('=') [1]) : 1
const dispatch = useDispatch()
const navigate = useNavigate()
const cart = useSelector((state) => state.cart)
const { cartItems } = cart
useEffect(() => {
if(id){
//useEffect working fine
dispatch(addToCart(id,qty))
}else{
//console.log('useEffect error')
}
}, [dispatch, id, qty])
const removeFromCartHandler = (id) => {
dispatch(removeFromCart(id))
}
const chechoutHandler = () => {
navigate('/login?redirect=/shipping')
}
return (
<Row>
<Col md={8}>
<h1>Shopping Cart</h1>
{ cartItems && cartItems.length === 0 ? (
<Message> Your cart is empty <Link to='/'>Go Back</Link></Message>
) : (
<ListGroup variant='flush'>
{cartItems.map(item => (
<ListGroup.Item key={item.product}>
<Row>
<Col md={2}>
<Image src={item.image} alt={item.name} className="cartItemPhoto" flude rounded />
</Col>
<Col md={3}>
<Link to={`/product/${item.product}`}>{item.name}</Link>
</Col>
<Col md={2}>₹{item.price}</Col>
<Col md={2}>
<Form.Control
as='select'
value={item.qty}
onChange={(e)=> dispatch(addToCart(item.product,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' varient='ligth' onClick={() => removeFromCartHandler(item.product)} >
<i className='fas fa-trash'></i>
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</Col>
<Col md={4}>
<Card>
<ListGroup varient = 'flush'>
<ListGroup.Item>
<h2>Subtotal ({cartItems.reduce((acc,item)=>acc+item.qty,0)}) items</h2>
₹{cartItems.reduce((acc,item)=>acc+item.qty * item.price,0).toFixed(2)}
</ListGroup.Item>
<ListGroup>
<Button type='button' className='btn-block' disabled={cartItems.length === 0} onClick={chechoutHandler}>
Proceed To Checkout
</Button>
</ListGroup>
</ListGroup>
</Card>
</Col>
</Row>
)
}
export default CartScreen
import React, { useState, useEffect } from 'react'
import { Link, useParams} from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import {Row, Col, Image, ListGroup, Card, Button, Form } from 'react-bootstrap'
import Rating from '../components/Rating'
import Loader from '../components/Loader'
import Message from '../components/Message'
import { listProductDetails, createProductReview } from '../actions/productActions'
import { useNavigate } from 'react-router-dom'
//import products from '../../../backend/data/products.js'
//import axios from 'axios'
import { PRODUCT_CREATE_REVIEW_RESET } from '../constants/productConstants'
const ProductScreen = ({ match}) => {
const {id} = useParams();
//const product = products.find((p) => p._id === id);
const [qty, setQty] = useState(1)
const [rating, setRating] = useState(0)
const [comment, setComment] = useState('')
const dispatch = useDispatch()
const productDetails = useSelector(state => state.productDetails)
const { loading, error, product } = productDetails
//console.log(product)
// reviews aasche sudhu
const userLogin = useSelector((state) => state.userLogin)
const { userInfo } = userLogin
const productReviewCreate = useSelector((state) => state.productReviewCreate)
const {
success: successProductReview,
loading: loadingProductReview,
error: errorProductReview,
} = productReviewCreate
useEffect(()=>{
if(successProductReview){
alert('Review Submitted!')
setRating(0)
setComment('')
dispatch({type: PRODUCT_CREATE_REVIEW_RESET})
}
dispatch(listProductDetails(id))
}, [dispatch, id, successProductReview])
//if(!product) return null;
// return ( <div>{product.name}</div> );
const history = useNavigate()
const addToCartHandler = () =>{
history(`/cart/${id}?qt=${qty}`)
}
const submitHandler = (e) => {
e.preventDefault()
dispatch(
createProductReview(id, {
rating,
comment,
})
)
}
//console.log(product)
return (
<>
<Link className='btn btn-outline-primary' to='/'>
Go Back
</Link>
{ loading ? (
<Loader/>
): error ? (
<Message variant='danger'> {error} </Message>
) : (
<>
<Row>
<Col md={6}>
{/* src={product.image} changed to -> {`${window.location.origin}/${product.image.name}`} */}
<Image src= {product.image} alt={product.name} fluid/>
</Col>
<Col md={3}>
<ListGroup variant='flust'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
{/*
<Rating
value={product.rating.toString()}
text={product.numReviews}
/>
*/}
</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>
{ product.countInStock > 0 && (
<ListGroup.Item>
<Row>
<Col>Qty</Col>
<Col>
<Form.Control
as='select'
value={qty}
onChange={(e)=>setQty(e.target.value)}
>
{[...Array(product.countInStock).keys()].map((x) => (
<option key={x+1} value={x + 1}>
{ x + 1 }
</option>
))}
</Form.Control>
</Col>
</Row>
</ListGroup.Item>
)}
<ListGroup.Item>
<Button
onClick={addToCartHandler}
className='btn btn-success btn-block btn-wd'
type='button'
disabled={product.countInStock===0}
>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
<Col md={6}>
<h2>Reviews</h2>
{product.reviews.length === 0 && <Message>No Reviews</Message>}
<ListGroup variant='flush'>
{product.reviews.map((review) => (
<ListGroup.Item key={review._id}>
<strong>{review.name}</strong>
<Rating value={review.rating} />
<p>{review.createdAt.substring(0, 10)}</p>
<p>{review.comment}</p>
</ListGroup.Item>
))}
<ListGroup.Item>
<h2>Write a Customer Review</h2>
{successProductReview && (
<Message variant='success'>
Review submitted successfully
</Message>
)}
{loadingProductReview && <Loader />}
{errorProductReview && (
<Message variant='danger'>{errorProductReview}</Message>
)}
{userInfo ? (
<Form onSubmit={submitHandler}>
<Form.Group controlId='rating'>
<Form.Label>Rating</Form.Label>
<Form.Control
as='select'
value={rating}
onChange={(e) => setRating(e.target.value)}
>
<option value=''>Select...</option>
<option value='1'>1 - Poor</option>
<option value='2'>2 - Fair</option>
<option value='3'>3 - Good</option>
<option value='4'>4 - Very Good</option>
<option value='5'>5 - Excellent</option>
</Form.Control>
</Form.Group>
<Form.Group controlId='comment'>
<Form.Label>Comment</Form.Label>
<Form.Control
as='textarea'
row='3'
value={comment}
onChange={(e) => setComment(e.target.value)}
></Form.Control>
</Form.Group>
<Button
disabled={loadingProductReview}
type='submit'
variant='primary'
>
Submit
</Button>
</Form>
) : (
<Message>
Please <Link to='/login'>sign in</Link> to write a review{' '}
</Message>
)}
</ListGroup.Item>
</ListGroup>
</Col>
</>
)}
</>
)
}
// Form.Control e add chilo, onChange{(e) => setQty(e.target.value)}
/*
<Form.Control
as='select'
value={qty}
onChange{(e) => setQty(e.target.value)}
>
// lec 32
*/
export default ProductScreen
import axios from 'axios'
import {
CART_ADD_ITEM,
CART_REMOVE_ITEM,
CART_SAVE_SHIPING_ADDRESS,
CART_SAVE_PAYMENT_METHOD
} from '../constants/cartConstants'
import { useParams } from 'react-router-dom'
export const addToCart = (id, qty) => async (dispatch, getState) => {
const {id} = useParams();
//console.log(`id working = ${id}`)
const { data } = await axios.get(`/api/products/${id}`)
//console.log(`data = ${data}`)
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) => (dispatch, getState) => {
dispatch({
type: CART_REMOVE_ITEM,
payload: id
})
localStorage.setItem('cartItems',JSON.stringify(getState().cart.cartItems))
}
export const saveShippingAdderss = (data) => (dispatch) => {
dispatch({
type: CART_SAVE_SHIPING_ADDRESS,
payload: data,
})
localStorage.setItem('shippingAddress',JSON.stringify(data))
}
export const savePaymentMethod = (data) => (dispatch) => {
dispatch({
type: CART_SAVE_PAYMENT_METHOD,
payload: data,
})
localStorage.setItem('paymentMethod',JSON.stringify(data))
}
import { CART_ADD_ITEM, CART_REMOVE_ITEM, CART_SAVE_PAYMENT_METHOD, CART_SAVE_SHIPING_ADDRESS } from '../constants/cartConstants.js'
import { PRODUCT_DETAILS_REQUEST, PRODUCT_LIST_REQUEST } from '../constants/productConstants.js'
export const cartReducer = (
state = { cartItems: [], shippingAddress: {} }, action) => {
//console.log(`action.type line on 6 = ${action.type}`)
switch(action.type){
case CART_ADD_ITEM:
//console.log(`CART_ADD_ITEM 3333 = ${action.type}`)
const item = action.payload
const existItem = state.cartItems.find( (x) => x.product === item.product)
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.product !==action.payload),
}
case CART_SAVE_SHIPING_ADDRESS:
return {
...state,
shippingAddress: action.payload,
}
case CART_SAVE_PAYMENT_METHOD:
return {
...state,
paymentMethod: action.payload,
}
default:
return state
}
}
I want to resolve the error so that I can add products to my cart. Thank you
I am new to react. I am not able to figure out the solution. On adding a product to the cart I am not able to see it in the cart scree. Please suggest any changes. The error I am getting is : state.cartItems is not iterable (cartReducers.js:26).
CART ACTION:
const { data } = await axios.get(`/api/products/${id}`)
console.log(data);
dispatch({
type: CART_ADD_ITEM,
payload: {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
})
console.log(data);
console.log(getState().cart.cartItems)
localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems))
}
REDUCER:
export const cartReducer = (
state = { cartItems: []},
action
) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload
const existItem = state.cartItems?.find((x) => x.product === item.product)
if (existItem) {
return {
...state.cartItems,
cartItems: state.cartItems?.map((x) =>
x.product === existItem.product ? item : x
),
}
} else {
return {
...state.cartItems,
cartItems: [...state.cartItems, item],
}
}
case CART_REMOVE_ITEM:
return {
...state.cartItems,
cartItems: state.cartItems.filter((x) => x.product !== action.payload),
}
default:
return state
}
}
Below is the cart component. I want to access all the cart items and calculate total quantity and price.
CART COMPONENT:
import React, { useEffect } from 'react'
import { Link } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { Row, Col, ListGroup, Image, Form, Button, Card } from 'react-bootstrap'
import Message from '../components/Message'
import { addToCart, removeFromCart } from '../actions/cartActions'
const CartScreen = ({ match, location, history }) => {
const productId = match.params.id
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) => {
dispatch(removeFromCart(id))
}
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) => (
<ListGroup.Item key={item.product}>
<Row>
<Col md={2}>
<Image src={item.image} alt={item.name} fluid rounded />
</Col>
<Col md={3}>
<Link to={`/product/${item.product}`}>{item.name}</Link>
</Col>
<Col md={2}>${item.price}</Col>
<Col md={2}>
<Form.Control
as='select'
value={item.qty}
onChange={(e) =>
dispatch(
addToCart(item.product, 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='fas fa-trash'></i>
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</Col>
<Col md={4}>
<Card>
<ListGroup variant='flush'>
<ListGroup.Item>
<h2>
Subtotal ({cartItems?.reduce((acc, item) => acc + item.qty, 0)})
items
</h2>
Rs.
{cartItems?.reduce((acc, item) => acc + item.qty * item.price, 0)
.toFixed(2)}
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
)
}
export default CartScreen
I have a OrderPrintReceiptScreen, on loading this screen I want the header and footer to not show on the screen. And after that I want to use window.print(); And this way a clean PDF Receipt can be achieved. But due to header and Footer they make it very dense and I'm not sure how to remove that on loading this OrderPrintReceiptScreen.
This is the layout of App
function App() {
return (
<Router>
<Header />
<main className="py-1">
<Container>
//.....
<Route path="/order-receipt/:id" component={OrderPrintReceiptScreen} />
//.....
</Container>
</main>
<Footer />
</Router>
);
}
export default App;
OrderPrintReceiptScreen.js
import React, { useEffect } from "react";
import { Button, Row, Col, ListGroup } from "react-bootstrap";
import { Page, Text, View, Document, StyleSheet } from "#react-pdf/renderer";
import { LinkContainer } from "react-router-bootstrap";
import { useDispatch, useSelector } from "react-redux";
import Message from "../components/Message";
import Loader from "../components/Loader";
import {
getOrderDetails,
// payOrder,
} from "../actions/orderActions";
import {
ORDER_PAY_RESET,
ORDER_DELIVER_RESET,
} from "../constants/orderConstants";
// Create styles
const styles = StyleSheet.create({
page: {
flexDirection: "row",
backgroundColor: "#E4E4E4",
},
section: {
margin: 10,
padding: 10,
flexGrow: 1,
},
});
function OrderPrintReceiptScreen({ match, history }) {
const orderId = match.params.id;
const dispatch = useDispatch();
const orderDetails = useSelector((state) => state.orderDetails);
const { order, error, loading } = orderDetails;
const orderPay = useSelector((state) => state.orderPay);
const { loading: loadingPay, success: successPay } = orderPay;
const orderDeliver = useSelector((state) => state.orderDeliver);
const { loading: loadingDeliver, success: successDeliver } = orderDeliver;
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
if (!loading && !error) {
order.itemsPrice = order.orderItems
.reduce((acc, item) => acc + item.price * item.qty, 0)
.toFixed(2);
}
useEffect(() => {
if (!userInfo) {
history.push("/login");
}
if (
!order ||
successPay ||
order._id !== Number(orderId) ||
successDeliver
) {
dispatch({ type: ORDER_PAY_RESET });
dispatch({ type: ORDER_DELIVER_RESET });
dispatch(getOrderDetails(orderId));
}
}, [dispatch, order, orderId, successPay, successDeliver]);
const printReceipt = (e) => {
e.preventDefault();
window.print();
};
return loading ? (
<Loader />
) : error ? (
<Message variant="danger">{error}</Message>
) : (
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>Section #1</Text>
</View>
<View style={styles.section}>
<Text>Section #2</Text>
</View>
<Row>
<Col md={10}>
<ListGroup variant="flush">
<ListGroup.Item>
<LinkContainer to={`/order-receipt/${order._id}`}>
<Button
variant="outline-success"
className="mx-4 my-4 btn-lg"
fluid
onClick={printReceipt}
>
Download Receipt
</Button>
</LinkContainer>
</ListGroup.Item>
<ListGroup.Item>Order ID : {order._id}</ListGroup.Item>
<ListGroup.Item>
Created On : {order.createdAt.substring(0, 10)},{" "}
{order.createdAt.substring(11, 19)}
</ListGroup.Item>
<ListGroup.Item>
Order Items:
{order.orderItems.length === 0 ? (
<Message variant="info">Order is empty</Message>
) : (
<ListGroup flush>
{order.orderItems.map((item, index) => (
<ListGroup.Item key={index}>
<Row>
<Col>{item.name}</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</ListGroup.Item>
<ListGroup variant="flush">
<ListGroup.Item>Name : {order.user.name}</ListGroup.Item>
<ListGroup.Item>
Phone Number : {order.shippingAddress.phoneNumber}
</ListGroup.Item>
<ListGroup.Item>
Shipping Address : {order.shippingAddress.address},{" "}
{order.shippingAddress.city}
{" "}
{order.shippingAddress.postalCode},{" "}
{order.shippingAddress.country}
</ListGroup.Item>
{order.isPaid ? (
<Message variant="light">
Payment Status : Paid On {order.paidAt.substring(0, 10)},{" "}
{order.paidAt.substring(11, 19)}
</Message>
) : (
<Message variant="warning">Not Paid</Message>
)}
<ListGroup variant="flush">
<ListGroup.Item>Payment Summary : </ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Items Price :</Col>
<Col>PKR {order.itemsPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping Price :</Col>
<Col>PKR {order.shippingPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Tax Amount :</Col>
<Col>PKR {order.taxPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Total Payable :</Col>
<Col> PKR {order.totalPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Total Paid :</Col>
<Col> PKR {order.totalPricePaid}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Remaining Amount:</Col>
<Col>
{" "}
PKR {Number(order.totalPrice) - order.totalPricePaid}
</Col>
</Row>
</ListGroup.Item>
</ListGroup>
{order.isDelivered ? (
<Message variant="light">
Delivery Status : Delivered on{" "}
{order.deliveredAt.substring(0, 10)},{" "}
{order.deliveredAt.substring(11, 19)}
</Message>
) : (
<Message variant="warning">Not Delivered</Message>
)}
</ListGroup>
</ListGroup>
</Col>
{/* <Col md={4}>
</Col> */}
</Row>
</Page>
);
}
export default OrderPrintReceiptScreen;
There are 2 ways to do this:
The first method is to check your matching URL before rendering:
render() {
const {match: {url}} = this.props;
if(url.startWith('/ignore-header-path') {
return null;
} else {
// your render jsx
}
}
The second method is to use #media print:
#media print {
/* Your print styles */
.header, .footer { display: none !important; }
}
You need to create a component Like a layout inside layout you can manage conditional header and footer Like this example.
Remove the Header footer from the App file.
Have a look I hope it's helpful
const OrderPrintReceiptScreen= (props) => {
return (
<Layouts
showFooter={false}
showHeader={false}
>
<Childeren {...props} />
</Layouts>
);
};
const Layouts= ({showFooter,showHeader,children}) =>{
return (
{showHeader && <Header/>}
{children}
{showFooter && <Footer/>}
)
}
........
You can use the window.location.pathname to get the current route after that do the validation on the route if the same hide the header else show the header.
{
window.location.pathname!=="/login"? <Header/> : null
}
I am creating a movie website that displays Modals of movies when you click on the movie card. I'm able to handle the "onCancel" which turns setActivateModal to false and closes the Modal, but I also want to allow the "Schedule" button to do something. The intended behavior is to have the "Schedule" button generate a different form in which I can fill out to "schedule" a movie with basic form entries that are then sent to my database. I'm not struggling with the form, but I'm struggling with how to handle generating one with the "Schedule" button. I'm unsure if you are allowed to do "nested" Modals, but any way it can be handled is fine.
import React, { useEffect, useState } from 'react';
import {
Layout,
Input,
Row,
Col,
Card,
Tag,
Spin,
Modal,
Typography,
Button,
} from 'antd';
import 'antd/dist/antd.css';
const { Content } = Layout;
const { Search } = Input;
const { Meta } = Card;
const TextTitle = Typography.Title;
const SearchBox = ({ searchHandler }) => {
return (
<Row>
<Col span={12} offset={6}>
<Search
placeholder="Search for movies to schedule!"
enterButton="Search"
size="large"
onSearch={value => searchHandler(value)}
/>
</Col>
</Row>
);
};
const MovieCard = ({
Title,
imdbID,
Poster,
ShowDetails,
DetailRequest,
ActivateModal,
}) => {
const clickHandler = () => {
ActivateModal(true);
DetailRequest(true);
fetch(`http://www.omdbapi.com/?i=${imdbID}&apikey=xxxxxxxx`)
.then(resp => resp)
.then(resp => resp.json())
.then(response => {
DetailRequest(false);
ShowDetails(response);
});
};
return (
<Col style={{ margin: '50px' }} span={3}>
<div>
<Card
style={{ width: 300 }}
cover={
<img
alt={Title}
src={
Poster === 'N/A'
? 'https://placehold.it/198x264&text=Image+Not+Found'
: Poster
}
/>
}
onClick={() => clickHandler()}
>
<Meta title={Title} />
</Card>
</div>
</Col>
);
};
const MovieDetail = ({
Title,
Actors,
Released,
Rated,
Runtime,
Genre,
Poster,
Plot,
}) => {
return (
<Row>
<Col span={11}>
<img
src={
Poster === 'N/A'
? 'https://placehold.it/198x264&text=Image+Not+Found'
: Poster
}
alt={Title}
/>
</Col>
<Col span={13}>
<Row>
<Col>
<TextTitle>{Title}</TextTitle>
</Col>
</Row>
<Row style={{ marginBottom: '.7em' }}>
<Col>{Actors}</Col>
</Row>
<Row style={{ marginBottom: '.7em' }}>
<Col>
<Tag>{Released}</Tag>
<Tag>{Rated}</Tag>
<Tag>{Runtime}</Tag>
<Tag>{Genre}</Tag>
</Col>
</Row>
<Row>
<Col>{Plot}</Col>
</Row>
</Col>
</Row>
);
};
const Loader = () => (
<div>
<Spin />
</div>
);
function Movies() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [query, setQuery] = useState('');
const [activateModal, setActivateModal] = useState(false);
const [details, setShowDetails] = useState(false);
const [detailRequest, setDetailRequest] = useState(false);
useEffect(() => {
setError(null);
setData(null);
fetch(`http://www.omdbapi.com/?s=${query}&apikey=xxxxxxxx`)
.then(resp => resp)
.then(resp => resp.json())
.then(response => {
if (response.Response === 'False') {
setError(response.Error);
} else {
setData(response.Search);
}
})
.catch(({ message }) => {
setError(message);
});
}, [query]);
return (
<div className="Movies">
<Layout className="layout">
<Content>
<div style={{ background: '#4a576e', padding: 60, minHeight: 300 }}>
<SearchBox searchHandler={setQuery} />
<br />
<Row justify="center">
{data !== null &&
data.length > 0 &&
data.map((result, index) => (
<MovieCard
ShowDetails={setShowDetails}
DetailRequest={setDetailRequest}
ActivateModal={setActivateModal}
key={index}
{...result}
/>
))}
</Row>
</div>
<Modal
title="Details"
centered
visible={activateModal}
onCancel={() => setActivateModal(false)}
/* onOk= {() => What do I put here? */
width={800}
footer={[
<Button key="cancel" onClick={() => setActivateModal(false)}>
Cancel
</Button>,
<Button
key="schedule" /* onClick={() => setActivateForm(true)} */
>
Schedule
</Button>,
]}
>
{detailRequest === false ? (
<MovieDetail {...details} />
) : (
<Loader />
)}
</Modal>
</Content>
</Layout>
</div>
);
}
export default Movies;
Assuming all the routes are set up properly in your App.js, add the following changes:
Add this to your import list:
import { Link } from "react-router-dom";
In the Movies function, add the const [activeForm, setActiveForm] = useState(false); as shown below
function Movies() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [query, setQuery] = useState('');
const [activateModal, setActivateModal] = useState(false);
const [details, setShowDetails] = useState(false);
const [detailRequest, setDetailRequest] = useState(false);
const [activateForm, setActivateForm] = useState(false);
In the return for your function, in the div tag right under the "Content" tag, add ActivateForm={setActivateForm} as shown below.
<div style={{ background: '#4a576e', padding: 60, minHeight: 300 }}>
<SearchBox searchHandler={setQuery} />
<br />
<Row justify="center">
{ data !== null && data.length > 0 && data.map((result, index) => (
<MovieCard
ShowDetails={setShowDetails}
DetailRequest={setDetailRequest}
ActivateModal={setActivateModal}
ActivateForm={setActivateForm}
key={index}
{...result}
/>
))}
</Row>
</div>
Finally, in the Modal tag, append to the "onOk" as such, and also in your Modal footer, add the following for "onClick".
<Modal
title='Details'
centered
visible={activateModal}
onCancel={() => setActivateModal(false)}
onOk={() => setActivateForm(true)}
width={800}
footer={[
<Button key="cancel" onClick={() => setActivateModal(false)}>
Cancel
</Button>,
<Button key="schedule" onClick={() =>setActivateForm(true)}><Link to='/ScheduleForm'>Schedule</Link ></Button>
]}
>
{ detailRequest === false ?
(<MovieDetail {...details} />) :
(<Loader />)
}
</Modal>
Please tell, how to carry out search in the list of components which code is specified below correctly?
The search should be performed by title or full description of the list item.
Component with list of Item components:
const PathItems = () => {
const dispatch = useDispatch();
const pathDescription = useSelector(state => state.firestore.ordered.pathDescription);
const handleClick = (path) => {
dispatch(selectPath(path));
}
if(pathDescription && pathDescription.length !== 0){
return (
<React.Fragment>
<Row>
<Col className="pl-0 border-right border-dark">
{pathDescription && pathDescription.map(item => (
<PathItem
key={item.id}
item={item}
onInfoChange={ handleClick }
/>
))}
</Col>
<Col>
<FullDecript/>
</Col>
</Row>
</React.Fragment>
)
} else {
return (
<h5 className="text-muted text-center text-middle">Add your first route</h5>
)
}
}
export default compose(firestoreConnect(()=> ['pathDescription']))(PathItems);
Item component code:
const PathItem = ({ item, onInfoChange }) => {
const handleClick = () => {
onInfoChange(item);
}
return (
<React.Fragment>
<Card as="a"
style={{cursor: 'pointer'}}
className={'mb-2'}
onClick={ handleClick }>
<Card.Body>
<Row className="align-items-center">
<Col xs={1}>
</Col>
<Col xs={7}>
<h5>{item.title}</h5>
{item.sDescript}
</Col>
<Col xs={4} className="text-right">
<label>{item.length}600 km</label>
</Col>
</Row>
</Card.Body>
</Card>
</React.Fragment>
);
}
export default PathItem;
General view of the described components
Thanks in advance)
...
const [searchQuery, setQuery] = useState("");
const inputEvent = (event) => {
const data = event.target.value;
console.log(pathDescription);
setQuery(data);
}
const filterItems = pathDescription && pathDescription.filter(item => {
return item.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.fDescript.toLowerCase().includes(searchQuery.toLowerCase());
})
...
<Col className="pl-0 border-right border-dark" style={divStyle}>
<InputGroup className="mb-3">
<FormControl
type="text"
placeholder="Type..."
aria-describedby="basic-addon2"
value={ searchQuery }
onChange={ inputEvent }
/>
<InputGroup.Append>
<Button variant="outline-secondary">
<img
alt="Logo"
src="https://cdn1.iconfinder.com/data/icons/app-user-interface-line/64/search_focus_user_interface_app_zoom-256.png"
width="25"
height="25"
className="d-inline-block align-top"/>
</Button>
</InputGroup.Append>
</InputGroup>
{filterItems.sort((a, b) => b.favorite - a.favorite).map(item => (
<PathItem
key={item.id}
item={item}
onInfoChange={ handleClick }
/>
))}
</Col>