Sowmiya P
Sowmiya P

Reputation: 72

Having Object value but react says undefined react redux

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

Answers (1)

Drew Reese
Drew Reese

Reputation: 202605

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

Upvotes: 1

Related Questions