I've been troubleshooting and I narrowed it down to the useEffect.
This code as-is won't fire the dispatch(getOrderByIdAction(id)) as shown in the redux dev tools.
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { useParams, Link } from "react-router-dom";
import CheckoutSteps from '../components/CheckoutSteps'
import { getOrderByIdAction } from '../state/actions/orderActions'
import Message from '../components/Message'
const OrderConfirmationScreen = () => {
const dispatch = useDispatch()
const { id } = useParams()
// From Order Confirmation State
const orderDetails = useSelector((state) => state.orderConfirmation.orderDetails)
const shippingAddress = orderDetails.shippingAddress
const paymentMethod = orderDetails.paymentMethod
const orderItems = orderDetails.orderItems
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc (cur.price * cur.qty)
}, 0)
const taxPrice = Number((itemPrice * .1).toFixed(2))
const totalPrice = taxPrice itemPrice
useEffect(() => {
if (id) {
dispatch(getOrderByIdAction(id))
}
},[])
return (
<div className="main-container lg:p-16">
<CheckoutSteps step1 step2 step3 />
<div className="max-w-6xl mx-auto p-8 my-8 w-full">
<div className="flex flex-col lg:flex-row">
<div className="place-order-container bg-gray-50 rounded-md shadow-2xl m-2 p-6 lg:p-12 w-full">
<h1 className="text-center font-semibold text-xl mt-4 mb-6">Place Order</h1>
{/* { error && <Message message={ "Hello" }/>} */}
<div className="order-header-group grid grid-cols-2 bg-white rounded-lg shadow-xl pt-4 pb-8 px-2">
<div className="order-shipping-section my-2 mx-4 lg:p-2">
<h4 className="font-medium my-4">Shipping Address: </h4>
<div className="address-group">
<h6 className="text-sm">{shippingAddress.address}</h6>
<h6 className="text-sm">{shippingAddress.city}, {shippingAddress.zipcode}</h6>
<h6 className="text-sm">{shippingAddress.phone}</h6>
{ shippingAddress &&
<Link to={'/shipping'} className="underline my-2 block">Edit</Link> }
</div>
</div>
<div className="order-payment-section my-2 lg:p-2">
<h4 className="font-medium my-4">Payment Method: </h4>
{ paymentMethod === 'Paypal' && <i className="fab fa-paypal fa-lg mr-2" style={{color: "#253B80"}}></i>}
<span className="text-sm">{paymentMethod}</span>
</div>
</div>
<div className="order-items-group my-3 bg-white rounded-lg shadow-2xl p-2 lg:p-4">
<div className="order-items-group rounded-md p-6">
{ orderItems.map((item, index) =>
<div key={item.productId} className="grid grid-cols-8 gap-2 items-center my-4">
<img src={item.image} alt={item.name} className="col-span-2 w-40 p-4" />
<h6 className="col-span-3 text-sm">{item.name}</h6>
<h6 className="col-span-1 text-sm text-center">{item.qty}</h6>
<h6 className="col-span-1 text-sm text-center">x</h6>
<h6 className="col-span-1 text-sm text-center">{item.price}</h6>
<hr className="my-4 col-span-8"/>
</div>
)}
</div>
</div>
</div>
<div className="order-total-section bg-gray-50 rounded-md shadow-2xl m-2 p-6 lg:pb-8 w-full lg:w-2/5 h-1/2">
<h4 className="font-semibold text-xl text-center my-8">Order Summary</h4>
<div className="bg-white rounded-lg shadow-2xl p-4">
<div className="items-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Items: </span>
<span className="col-span-1 my-2 block text-sm">${itemPrice} </span>
</div>
<div className="shipping-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Shipping: </span>
<span className="col-span-1 my-2 block text-sm">FREE </span>
</div>
<div className="tax-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Tax: </span>
<span className="col-span-1 my-2 block text-sm">${taxPrice} </span>
</div>
<hr className="my-2" />
<div className="total-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm font-semibold">Total: </span>
<span className="col-span-1 my-2 block text-sm font-semibold">${totalPrice} </span>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default OrderConfirmationScreen;
If I just run dispatch without useEffect, it will fire off with a success and I get all the order info on my reducer. So I know it has something to do with the useEffect.
Unfortunately, then I get an error that says orderDetails is undefined because I need useEffect to run it before the component renders?
Not sure what I did wrong
EDIT:
instructor's orderScreen.js
const OrderScreen = ({ match, history }) => {
const orderId = match.params.id
const [sdkReady, setSdkReady] = useState(false)
const dispatch = useDispatch()
const orderDetails = useSelector((state) => state.orderDetails)
const { order, loading, error } = 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) {
// Calculate prices
const addDecimals = (num) => {
return (Math.round(num * 100) / 100).toFixed(2)
}
order.itemsPrice = addDecimals(
order.orderItems.reduce((acc, item) => acc item.price * item.qty, 0)
)
}
useEffect(() => {
if (!userInfo) {
history.push('/login')
}
const addPayPalScript = async () => {
const { data: clientId } = await axios.get('/api/config/paypal')
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`
script.async = true
script.onload = () => {
setSdkReady(true)
}
document.body.appendChild(script)
}
if (!order || successPay || successDeliver || order._id !== orderId) {
dispatch({ type: ORDER_PAY_RESET })
dispatch({ type: ORDER_DELIVER_RESET })
dispatch(getOrderDetails(orderId))
} else if (!order.isPaid) {
if (!window.paypal) {
addPayPalScript()
} else {
setSdkReady(true)
}
}
}, [dispatch, orderId, successPay, successDeliver, order])
const successPaymentHandler = (paymentResult) => {
console.log(paymentResult)
dispatch(payOrder(orderId, paymentResult))
}
const deliverHandler = () => {
dispatch(deliverOrder(order))
}
return loading ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : ( jsx
edited code
OrderConfirmation.js
const OrderConfirmationScreen = () => {
const dispatch = useDispatch()
const { id } = useParams()
// From Order Confirmation State
const orderConfirmation = useSelector((state) => state.orderConfirmation)
const { loading, error, orderDetails } = orderConfirmation
useEffect(() => {
if(id) {
console.log(id);
dispatch(getOrderByIdAction(id))}
},[id])
const shippingAddress = orderDetails.shippingAddress
const paymentMethod = orderDetails.paymentMethod
const orderItems = orderDetails.orderItems
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc (cur.price * cur.qty)
}, 0)
const taxPrice = Number((itemPrice * .1).toFixed(2))
const totalPrice = taxPrice itemPrice
return loading? (<h3>loading..</h3>) :
( jsx
CodePudding user response:
Since the data is not in the store when you render the component, you have to wait for the http request to complete.
You can return null
when orderDetails
is undefined
, but consider adding a loading
and error
state to give a better user experience and make it easier for you to render a loading component or displaying an error
const OrderConfirmationScreen = () => {
const dispatch = useDispatch();
const { id } = useParams();
// From Order Confirmation State
const orderDetails = useSelector(
state => state.orderConfirmation.orderDetails,
);
useEffect(() => {
if (id) {
dispatch(getOrderByIdAction(id));
}
}, [id]);
// This is the important part.
// consider replacing this condition with loading and error from your state
if (!orderDetails) return <h3>Loading ...</h3>;
const { shippingAddress, paymentMethod, orderItems } = orderDetails;
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc cur.price * cur.qty;
}, 0);
const taxPrice = Number((itemPrice * 0.1).toFixed(2));
const totalPrice = taxPrice itemPrice;
return jsx...
};