Reputation: 72
const orderId = props.match.params.id;
const orderDetails = useSelector((state) => state.orderDetails);
const { loading, error, order } = orderDetails;
const dispatch = useDispatch();
const toPrice = (num) => Number(num.toFixed(2));
useEffect(() => {
dispatch(detailsOrder(orderId));
}, [dispatch, orderId]);
return loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger"></MessageBox>
) : (
<div>
<div>
<h2>Order ID: {order._id}</h2>
<h4>{order.shippingAddress.city}</h4>
</div>
</div>
);
When I display id only , there is no issue . Its displays fine. But When I use shippingAddress object it throws an error , " city is undefined " and also action is also not dispatched. But I have shippingAddress object with elements in redux store when I display id only. Action Creator is:
export const detailsOrder = (orderId) => async (dispatch, getState) => {
dispatch({ type: ORDER_DETAILS_REQUEST, payload: orderId });
try {
const {
userSignin: { userInfo },
} = getState();
const { data } = await Axios.get(`/api/orders/${orderId}`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: ORDER_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: ORDER_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Reducer is:
export const orderDetailsReducer = (state = { order: [] }, action) => {
switch (action.type) {
case ORDER_DETAILS_REQUEST:
return { loading: true };
case ORDER_DETAILS_SUCCESS:
return { loading: false, order: action.payload };
case ORDER_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
API call response is like that:
orderDetails:{
order:{
"shippingAddress": {
"fullName": "Sowmiya Pachiappan",
"address": "xxxx",
"city": "xxxx",
"postalCode": "xxxxx",
"country": "xxxx"
},
"isPaid": false,
"isDelivered": false,
"_id": "6045ca2feca0270cf433162a",
"orderItems": [
{
"_id": "6045ca2feca0270cf433162b",
"product": "6042002e7cd2060fe4c59227",
"qty": 5
}
],
"paymentMethod": "Pay on Delivery",
"itemsPrice": 250,
"shippingPrice": 0,
"totalTaxPrice": 12.5,
"totalPrice": 272.5,
"user":"5ff2aae21a184c0b00b59637",
"status": "Order Placed",
"createdAt": "2021-03-08T06:54:39.481Z",
"updatedAt": "2021-03-08T06:54:39.481Z",
"__v": 0
}
}
Upvotes: 2
Views: 795
Reputation: 202605
The basic issue is that your initial state doesn't match what you attempt to access on the initial render cycle when you dispatch an action to presumably populate the state.
The initial reducer state is { order: [] }
. Initially order
is an array, but you later update order
to be an object.
useSelector((state) => state.orderDetails)
provides you a valid, truthy orderDetails
object, so accessing any property from this is ok.
orderDetails.order.shippingAddress // no error, value undefined
But when accessing deeper will throw the error you see
orderDetails.order.shippingAddress.city // error, can't access city of undefined
Provide valid initial reducer state. Here you will want to provide at least a property for anything that is accessed on the initial render before state it populated/updated.
const initialState = {
error: null,
loading: false,
order: {
shippingAddress: {},
},
};
const orderDetailsReducer = (state = initialState, action) => { ..... }
This will allow your initial render to work as now orderDetails.data.shippingAddress
will be defined.
The alternative would be to use null-checks to guard against undefined accesses. A null-check is required at each level of depth into an object.
You can use guard clauses:
order && order.shippingAddress && order.shippingAddress.city
Or you can use Optional Chaining:
order?.shippingAddress?.city
Upvotes: 1