I can't get this React component to work. The useEffect is not running so I could the fill the redux state, read the necessary information from that state and display it in the component. I get a lot of errors in the console about a lot of variables being undefined. I need somehow to fill the state with the dispatch and render the component after that. How can I solve this problem?
Console error
import { useEffect } from 'react'
import { Link, useParams } from 'react-router-dom'
import {
Row,
Col,
Image,
ListGroup,
Card,
ListGroupItem,
} from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux'
import Message from '../components/Message'
import Loader from '../components/Loader'
import { getOrderDetails } from '../features/orders/orderDetailsSlice'
const OrderScreen = () => {
const dispatch = useDispatch()
const params = useParams()
const orderId = params.id
const orderDetails = useSelector((state) => state.orderDetails)
const { order, isLoading, isError, message } = orderDetails
if (!isLoading) {
// Calculate items price
const addDecimals = (num) => {
return (Math.round(num * 100) / 100).toFixed(2)
}
var itemsPrice = addDecimals(
order.orderItems.reduce((acc, item) => acc item.price * item.qty, 0)
)
}
useEffect(() => {
dispatch(getOrderDetails(orderId))
}, [dispatch, orderId])
return isLoading ? (
<Loader />
) : isError ? (
<Message variant='danger'>{message}</Message>
) : (
<>
<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>Address: </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='danger'>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='danger'>Not paid</Message>
)}
</ListGroup.Item>
<ListGroup.Item>
<h2>Order Items</h2>
{order.orderItems.length === 0 ? (
<Message>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} = $
{Number(item.qty * item.price).toFixed(2)}
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={4}>
<Card>
<ListGroup variant='flush'>
<ListGroupItem>
<h2>Order Summary</h2>
</ListGroupItem>
<ListGroup.Item>
<Row>
<Col>Items</Col>
<Col>${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>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}
export default OrderScreen
I removed the calculations from the code and I get a different error in the console.
import { useEffect } from 'react'
import { Link, useParams } from 'react-router-dom'
import {
Row,
Col,
Image,
ListGroup,
Card,
ListGroupItem,
} from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux'
import Message from '../components/Message'
import Loader from '../components/Loader'
import { getOrderDetails } from '../features/orders/orderDetailsSlice'
const OrderScreen = () => {
const dispatch = useDispatch()
const params = useParams()
const orderId = params.id
const orderDetails = useSelector((state) => state.orderDetails)
const { order, isLoading, isError, message } = orderDetails
useEffect(() => {
dispatch(getOrderDetails(orderId))
}, [dispatch, orderId])
return isLoading ? (
<Loader />
) : isError ? (
<Message variant='danger'>{message}</Message>
) : (
<>
<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>Address: </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='danger'>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='danger'>Not paid</Message>
)}
</ListGroup.Item>
<ListGroup.Item>
<h2>Order Items</h2>
{order.orderItems.length === 0 ? (
<Message>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} = $
{Number(item.qty * item.price).toFixed(2)}
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={4}>
<Card>
<ListGroup variant='flush'>
<ListGroupItem>
<h2>Order Summary</h2>
</ListGroupItem>
<ListGroup.Item>
<Row>
<Col>Items</Col>
{/* <Col>${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>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}
export default OrderScreen
Error with calculations removed
If I return a simple div, everything works fine and the state is filled. Div return
It seems to have a problem with these 3 paragraphs and I don't know why, because I have those variables in the state. If I comment them out, everything works fine.
<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>Address: </strong>
{order.shippingAddress.address}, {order.shippingAddress.city}{' '}
{order.shippingAddress.postalCode},{' '}
{order.shippingAddress.country}
</p>
I found the solution. I just needed to specify the objects and arrays within the order object in my initial state.
const initialState = {
order: {
orderItems: [],
user: {},
shippingAddress: {}
},
isError: false,
isSuccess: false,
isLoading: false,
message: '',
}
CodePudding user response:
I think the issue comes from isLoading that is undefined before dispatch your action getOrderDetails. So you enter in your loop.. Try by adding an initialState to orderItems as an empty array or by default turn isLoading as true.
CodePudding user response:
Try replacing this:
if (!isLoading) {
// Calculate items price
const addDecimals = (num) => {
return (Math.round(num * 100) / 100).toFixed(2)
}
var itemsPrice = addDecimals(
order.orderItems.reduce((acc, item) => acc item.price * item.qty, 0)
)
}
With this:
var itemsPrice = useMemo(() => {
if (orderDetails?.order) {
// Calculate items price
const addDecimals = (num) => {
return (Math.round(num * 100) / 100).toFixed(2)
}
return addDecimals(
orderDetails.order.orderItems.reduce((acc, item) => acc item.price * item.qty, 0)
)
}
}, [orderDetails])
You are accessing order.orderItems
before the data is fetched.
CodePudding user response:
I found the solution. I just needed to specify the objects and arrays within the order object in my initial state.
const initialState = {
order: {
orderItems: [],
user: {},
shippingAddress: {}
},
isError: false,
isSuccess: false,
isLoading: false,
message: '',
}