sinusGob sinusGob - 4 months ago 112
Javascript Question

TypeError: Cannot read property 'map' of undefined in react-redux

Im new to React and Redux and still kinda confused a little bit.

My goal is to render a bunch of json datas in the HTML by using GET request. I'm using react and redux to manage the state of the objects, but I believe my problem is that the data is not even there

so basically whenever someone request a URL /courses , he/she will see bunch of data in json.

I get the error in the component


TypeError: Cannot read property 'map' of undefined


Here's the code

Action

export function getCourses() {
return (dispatch) => {

return fetch('/courses', {
method: 'get',
headers: { 'Content-Type', 'application/json' },
}).then((response) => {
if (response.ok) {
return response.json().then((json) => {
dispatch({
type: 'GET_COURSES',
courses: json.courses
});
})
}
});
}
}


Reducer

export default function course(state={}, action) {

switch (action.type) {
case 'GET_COURSES':
return Object.assign({}, state, {
courses: action.courses
})
default:
return state;
}


}


Component

import React from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';


class Course extends React.Component {



allCourses() {
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);

});
}

render() {
return (
<div>
<ul>
{ this.allCourses() }
</ul>

</div>
)
}
}

const mapStateToProps = (state) => {
return {
courses: state.courses
}
}

export default connect(mapStateToProps)(Course);


Index reducer, where i combine everything

import { combineReducers } from 'redux';
import course from './course';

export default combineReducers({
course,
});


Configure Store , where i store the intial state and the reducer

import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';

export default function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
typeof window == 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
)
);

return store;
}


I believe why the data is not there is because i didn't call the action? any help would be appreciated.

Answer

mapStateToProps takes the root state as an argument (your index reducer, which is also the root reducer), not your course reducer. As far as I can tell this is the structure of your store:

-index <- This is the root reducer
  -course

So to get the courses from that state, in your component:

// state is the state of the root reducer
const mapStateToProps = (state) => {
  return {
    courses: state.course.courses
  }
}

Also, you might consider initialising the state of the course reducer with an empty array of courses, so if you have to render the component before the action is fired, you won't get the error.

const initialState = {
    courses: []
};

export default function course(state= initialState, action) {

    ...

}

Finally, you're not firing the action at all, so you will never actually get the courses, I assume you want them to be retrieved once the Course component is loaded, for that you can use the componentDidMount event in your component.

First of all, you need to map the action to a property of the component

// Make sure you import the action
import { getCourses } from './pathToAction';

...

const mapDispatchToProps = (dispatch) => {
  return {
    onGetCourses: () => dispatch(getCourses())
  };
}

// Connect also with the dispatcher
export default connect(masStateToProps, mapDispatchToProps)(Course);

Now call the onGetCourses property when the component mounts

class Course extends React.Component {

    componentDidMount() {
      this.props.onGetCourses();
    }

    ...

}