Antonio Pavicevac-Ortiz
Antonio Pavicevac-Ortiz

Reputation: 7739

Value is undefined from action while using react-redux, instead of payload

I am building an app using React, React-redux, Redux, Express, and Sequelize for a project for school.

In my other views I got the data back, to insert and update my view accordingly, but in this case I'm getting undefined.

I just switched from redux to react-redux so I apologize in advance!

This is a screenshot of my console:

enter image description here

This my reducer for the SingleCountry, with my action creators and thunks...

'use strict';

import axios from 'axios';

// ACTION TYPES
const GET_COUNTRY = 'GET_COUNTRY';

// ACTION CREATORS
export function getCountry(oneCountry) {
  const action = {
    type: GET_COUNTRY,
    oneCountry,
  };
  return action;
}

//THUNK CREATORS

export function fetchCountry(countryId) {
  return function thunk(dispatch) {
    return axios
      .get('/api/countries/' + `${countryId}`)
      .then(res => res.data)
      .then(country => {
        const action = getCountry(country);
        dispatch(action);
      });
  };
}

// REDUCER
const reducer = function(state = [], action) {
  switch (action.type) {
    case GET_COUNTRY:
      return action.oneCountry;
    default:
      return state;
  }
};

export default reducer;

This is the reducer for Countries:

'use strict';

import axios from 'axios';

// ACTION TYPES
const GET_COUNTRIES = 'GET_COUNTRIES';

// ACTION CREATORS
export function getCountries(countries) {
  const action = {
    type: GET_COUNTRIES,
    countries,
  };
  return action;
}

//THUNK CREATORS
export function fetchCountries() {
  return function thunk(dispatch) {
    return axios
      .get('/api/countries')
      .then(res => res.data)
      .then(countries => dispatch(getCountries(countries)))
      .catch(console.error);
  };
}

// REDUCER
const reducer = function(state = [], action) {
  switch (action.type) {
    case GET_COUNTRIES:
      return action.countries;
    // return { ...state, countries: action.countries };
    default:
      return state;
  }
};

export default reducer;

And this is the reducer for the TopFiveCountries:

'use strict';

import axios from 'axios';

// ACTION TYPES
const GET_TOP_COUNTRIES_BY_GFI = 'GET_TOP_COUNTRIES_BY_GFI';

// ACTION CREATORS
export function getTopFiveCountriesByGFI(topFiveCountries) {
  const action = {
    type: GET_TOP_COUNTRIES_BY_GFI,
    topFiveCountries,
  };
  return action;
}

//THUNK CREATORS
export function fetchTopFiveCountriesByGFI() {
  return function thunk(dispatch) {
    return axios
      .get('/api/countries/top-five-countries')
      .then(res => res.data)
      .then(countries => {
        const action = getTopFiveCountriesByGFI(countries);
        dispatch(action);
      })
      .catch(console.error);
  };
}

// REDUCER
const reducer = function(state = [], action) {
  switch (action.type) {
    case GET_TOP_COUNTRIES_BY_GFI:
      return action.topFiveCountries;
    // return { ...state, topFiveCountries: action.topFiveCountries };
    default:
      return state;
  }
};

export default reducer;

This the view for SingleCountry question:

import React, { Component } from 'react';
import { fetchCountry } from '../reducers/oneCountry';
import { connect } from 'react-redux';
import store from '../store';

class SingleCountry extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const flagStyle = {
      height: '50px',
      width: '100px',
    };

    const oneCountry = this.props.oneCountry;
    return (
      <div className="row">
        <div className="twelve columns">
          <h2> - Single Country -</h2>

          <table className="u-full-width">
            <thead>
              <tr>
                <th>Name</th>
                <th>GFI</th>
                <th>Flag </th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>{oneCountry.name}</td>
                <td>{oneCountry.GFI}</td>
                <td>
                  <img style={flagStyle} src={oneCountry.flagUrl} />
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    );
  }
}

const mapDispatch = dispatch => ({
  fetchCountry: countryId => {
    dispatch(fetchCountry(countryId));
  },
});

const mapState = state => ({
  oneCountry: state.oneCountry,
});

export default connect(mapState, mapDispatch)(SingleCountry);

My gut is telling me its either in my axios request or maybe some of the syntax for mapping and dispatching.

As an aside I do at least get a partial render (see below)...And no errors. Are there any precautions one could do for better diagnose?

enter image description here

Any help will be appreciated!

UPDATE This is how my store.js file looks like...

import { createStore, applyMiddleware, combineReducers } from 'redux';
import loggingMiddleware from 'redux-logger'; // https://github.com/evgenyrodionov/redux-logger
import thunkMiddleware from 'redux-thunk'; // https://github.com/gaearon/redux-thunk
import { composeWithDevTools } from 'redux-devtools-extension';

import topFiveCountriesByGFIReducer from './reducers/topFiveCountriesByGFI';
import countriesReducer from './reducers/countries';
import oneCountryReducer from './reducers/oneCountry';

const middleware = [loggingMiddleware, thunkMiddleware];

const rootReducer = combineReducers({
  topFiveCountries: topFiveCountriesByGFIReducer,
  countries: countriesReducer,
  oneCountry: oneCountryReducer,
});

export default createStore(rootReducer, composeWithDevTools(applyMiddleware(...middleware)));

UPDATE

Recent error I'm getting:

enter image description here

Upvotes: 0

Views: 745

Answers (1)

Andrew Li
Andrew Li

Reputation: 57964

So you aren't dispatching your action. You have to dispatch the action, and since it's asynchronous, it'd be good to show some kind of UI to indicate you're fetching from an API of some sort. Here's a trimmed down version:

class SingleCountry extends Component {
  constructor(props) {
    super(props)

    this.state = {
      loading: true
    }
  }

  componentDidMount() {
    const countryId = this.props.match.params.id;
    dispatch(this.props.fetchCountry(countryId))
      .then(() => {
        this.setState({
          loading: false
        });
      });
  }

  render() {
    const oneCountry = this.props.oneCountry;
    const loading = this.state.loading;
    return (
      <div className="row">
        <div className="twelve columns">
          <h2> - Single Country -</h2>

          {
            !loading ?
              <table className="u-full-width">
                <thead>
                  <tr>
                    <th>Name</th>
                    <th>GFI</th>
                    <th>Flag </th>
                  </tr>
                </thead>
                <tbody>
                  <tr>
                    <td>{oneCountry.name}</td>
                    <td>{oneCountry.GFI}</td>
                    <td>
                      <img style={flagStyle} src={oneCountry.flagUrl} />
                    </td>
                  </tr>
                </tbody>
              </table>
            :
              <img src={LOADING ICON HERE} />
          }
        </div>
      </div>
    );
  }
}

const mapState = (state, ownProps) => ({
  oneCountry: state.oneCountry,
  ...ownProps
});

What this does is initially set state of loading to true. Then, when the component mounts, your request is sent to the API. Meanwhile, while the request is working, a loading icon will show up. Once the request is done, the then of the dispatch will execute and set state of loading to false. This will, in turn, rerender your component and show the table, with the appropriate data.

Also, notice mapState now uses a second argument - ownProps. The thing is, when you connect a component to the Redux store, you lose any props that are passed to the component. In this case, React Router passes the matched parameters to SingleCountry but it's lost due to connect. This can be solved by using the second argument of either mapState or mapDispatch. The second argument contains allow the props passed to the connected component, and you can spread them into the returned object to access them in your component.

Upvotes: 1

Related Questions