2
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
     }
   }
6
  • Is the error that "city is undefined" or is it rather something like "cannot access city of undefined", implying that order.shippingAddress is undefined? The latter makes more sense IMO. Can you include your orderDetails reducer code and detailsOrder action creator? Commented Mar 10, 2021 at 8:33
  • Error is : TypeError: Cannot read property 'city' of undefined Commented Mar 10, 2021 at 8:42
  • Your initial state is { order: [] } and as an array it likely won't ever have an _id or shippingAddress "property". Does action.payload mutate the state invariant in the SUCCESS/FAIL cases? What is the response shape? It looks like state.orderDetails changes from an array to an object. Commented Mar 10, 2021 at 8:53
  • when I print shippingaddress object in console It gives output like this: address: "xxx" city: "xxxx" country: "xxx" fullName: "xxxx" postalCode: "xxxx" Commented Mar 10, 2021 at 9:51
  • If I use direct properties of the "order" object, it does not throw any error. If I try to access nested object i.e. "order.shippingAddress" object, it throws error Commented Mar 10, 2021 at 9:57

1 Answer 1

1

Issue

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

Solution

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.