johnirle
johnirle

Reputation: 13

Accessing Redux State through this.props is throwing "Cannot read property name of undefined."

Redux Dev Tools view Edit: Here is a picture of redux devtools. It is populating state with an array of objects. Anytime I try to access an object in the array I get this error React Error

I have a server that is getting data from yelp when it receives a get request and it sends the response from yelp up to the client.

When I try to access the server response that was called from the getFoodPlans() function, it shows up as state in the redux webtools but says "Cannot find property name of undefined", if you try to access it through props.

If I use an axios call to the endpoint and set local state with the response, I can access all the properties. I have a feeling that it has to do with the server, because if I send the whole businesses object from the server I get the same error with axios where I can't access a property of undefined.

Does this have to do with how I'm sending the data from the server to the client?

Thank you.

Server:

 router.get('/food', (req, res) => {
  const position = Math.floor(Math.random() * 15);
  axios
   .get(`${foodSearch}location=kansascity
         &&radius=${radius}&&cost=1,2,3&&categories=food`, {
         headers: { Authorization: `Bearer ${apiKey}` }
    })
    .then(result => {
      res.json(result.data.businesses[position]);
    })
    .catch(err => console.log(err));
});

PlanActions.js:

import axios from 'axios';
import { GET_FOOD_PLANS, PLAN_LOADING, GET_ERRORS, GET_ACTIVITY_PLANS } from 
'./types';

// Get Plans
export const getFoodPlans = () => dispatch => {
  dispatch(setPlanLoading());
  axios
    .get('/api/data/food')
    .then(res =>
      dispatch({
        type: GET_FOOD_PLANS,
        payload: res.data
      })
    )
    .catch(err =>
      dispatch({
        type: GET_FOOD_PLANS,
        payload: err
      })
    );
};

planReducer.js:

import { GET_FOOD_PLANS, GET_ACTIVITY_PLANS, PLAN_LOADING } from 
'../actions/types';

const initalState = {
  food: {},
  activities: {},
  triggered: false,
  loading: false
};

export default function(state = initalState, action) {
  switch (action.type) {
    case PLAN_LOADING:
      return {
        ...state,
        loading: true
      };
    case GET_FOOD_PLANS:
      return {
        ...state,
        food: action.payload,
        loading: false
      };
    case GET_ACTIVITY_PLANS:
      return {
        ...state,
        activities: action.payload,
        loading: false
      };
    default:
      return state;
  }
}

PlanTomorrow.js:

import React from 'react';
import PropTypes from 'prop-types';
import { getFoodPlans } from '../../actions/planActions';
import { connect } from 'react-redux';
import axios from 'axios';

import ResultBox from '../Resultbox/ResultBox';

class PlanTomorrow extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      load: false,    
      activity1: {},
    };
  }

  componentWillMount() {
    this.props.getFoodPlans();
    axios.get('/api/data/activity').then(res => this.setState({ activity1: 
    res.data }));
  }

  render() {
    return (
      <div className="tomorrowform">
        <h2>Tomorrows Plan:</h2>
        <ul className="tomorrowform-data">
          <li id="item1">
            <ResultBox
              activity={this.props.plan.food.name}
              phone={this.props.plan.food.display_phone}
              rating={this.props.plan.food.rating}
            />
          </li>
          <li id="item2">
            <ResultBox
              activity={this.state.activity1.name}
              phone={this.state.activity1.display_phone}
              rating={this.state.activity1.rating}
            />
         </li>
        </ul>
      </div>
    );
  }
}

PlanTomorrow.propTypes = {
  auth: PropTypes.object.isRequired,
  plan: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  auth: state.auth,
  plan: state.plan
});

export default connect(
  mapStateToProps,
  { getFoodPlans }
)(PlanTomorrow);

Upvotes: 1

Views: 759

Answers (1)

Andrey Medvedev
Andrey Medvedev

Reputation: 1991

Two points:

  1. Do your API call from the componentDidMount(). It's the best for external API calls. Adviced by react docs.
  2. You should call your action like this.props.getFoodPlans() as you pass it to your component via connect.
  3. Because you are doing external call in the component your data won't be in the right shape on the first render. You should check that too. Add this before the return in your render method.
const isFoodExists = !this.props.plan.food || !this.props.plan.food.length;

if (this.props.loading || !isFoodExists) {
  return null;
}

Here you should return the data for food as an array. And pass it as an array to your components.

Upvotes: 2

Related Questions