Zeusox
Zeusox

Reputation: 8448

React JS & Redux: Rendering Multiple Components within the Provider Throws an Undefined Error

I am working on this React JS Redux app where I have one store with 2 action files, 2 reducer files and 2 action type files. Everything works good when I am rendering only one component of these action files. If I render both as you see below, I get that the second component's object is undefined:

function App() {
  return (
    <Provider store={store}>
    <div className="App">
      < Posts />
      < Users />
    </div>
    
    </Provider>
  );
}

Rendering the above, throws the following error:

Cannot read property 'map' of undefined

If I render the above either omitting </ Posts> or </ Users> it works fine and it fetches the related data of each component.

Is it not possible to render two components within the Provider?

For more context, this is what I have in my application so for:

Store.js Includes:

 import { createStore, compose, applyMiddleware, combineReducers } from 
 "redux";
    import reduxThunk from "redux-thunk";
    import postReducer from "../reducers/postReducer";
    import userReducer from "../reducers/userReducer";
    
    const rootReducer = combineReducers({
      posts: postReducer,
      users: userReducer
    });
    
    const store = createStore(rootReducer,  
    compose(applyMiddleware(reduxThunk)));
    
    export default store;

App.js includes:

import './App.css';
import React from "react";

import store from './store/store.js';
import { Provider } from "react-redux";
import Posts from "./components/postComponent.js"
import Users from "./components/userComponent.js"


function App() {
  return (
    <Provider store={store}>
    <div className="App">
      < Posts />
      < Users />
    </div>
    
    </Provider>
  );
}

export default App;

actionTypes.js includes:

export const FETCH_POSTS_STARTED = 'FETCH_POSTS_STARTED';
export const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS';
export const FETCH_POSTS_FAILURE = 'FETCH_POSTS_FAILURE';
export const FETCH_USERS_STARTED = 'FETCH_POSTS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_POSTS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_POSTS_FAILURE';

userAction.js includes:

import {
    FETCH_USERS_STARTED,
    FETCH_USERS_SUCCESS,
    FETCH_USERS_FAILURE 
} from '../actions/actionTypes';
import axios from "axios";

export const fetchUsers = () => {
    return dispatch => {
       dispatch(fetchUserStarted());
       axios.get('https://jsonplaceholder.typicode.com/users')
       .then(response => {
           // dispatch response here
           dispatch(fetchUserSuccess(response.data));
           console.log('Data', response.data);
       })
       .catch(error => {
           console.log('Error getting user API ' + error);
           dispatch(fetchUserFailure(error.message));
       })
    }
}


const fetchUserStarted = () => {
   return{
       type: FETCH_USERS_STARTED,
       payload: {
           isLoading: true
       }
   }
}

const fetchUserSuccess = users => {
  return{
      type: FETCH_USERS_SUCCESS,
      payload: {
        users
      }
  }
}

const fetchUserFailure = error => {
    return{
        type: FETCH_USERS_FAILURE,
        payload: {
            error
          }
    }
}

postAction.js includes:

import {
  FETCH_POSTS_STARTED,
  FETCH_POSTS_FAILURE,
  FETCH_POSTS_SUCCESS
} from "./actionTypes";
import axios from "axios";

export const fetchPosts = () => {
  return dispatch => {
    dispatch(fetchPostsStarted());

    axios
      .get("https://jsonplaceholder.typicode.com/posts")
      .then(res => {
        dispatch(fetchPostsSuccess(res.data));
      })
      .catch(err => {
        dispatch(fetchPostsFailed(err.message));
      });
  };
};

const fetchPostsStarted = () => {
  return {
    type: FETCH_POSTS_STARTED,
    payload: {
      isLoading: true
    }
  };
};

const fetchPostsSuccess = posts => {
  return {
    type: FETCH_POSTS_SUCCESS,
    payload: {
      posts
    }
  };
};

const fetchPostsFailed = error => {
  return {
    type: FETCH_POSTS_FAILURE,
    payload: {
      error
    }
  };
};

userReducer.js includes:

import {
    FETCH_USERS_STARTED,
    FETCH_USERS_SUCCESS,
    FETCH_USERS_FAILURE
} from "../actions/actionTypes.js";


const initialState = {
    users: [],
    loading: false,
    error: null
}

export default function (state = initialState, action) {
    switch (action.type) {

        case FETCH_USERS_STARTED:
            return {
                ...state,
                loading: true
            };
        case FETCH_USERS_SUCCESS:
            return {
                ...state,
                loading: false,
                error: null,
                users: action.payload.users
            };
        case FETCH_USERS_FAILURE:
            return {
                ...state,
                error: action.payload.error
            };

        default:
            return state

    }
}

postReducer.js includes:

import {
  FETCH_POSTS_STARTED,
  FETCH_POSTS_SUCCESS,
  FETCH_POSTS_FAILURE
} from "../actions/actionTypes.js";


const initialState = {
  posts: [],
  loading: false,
  error: null
};

export default function (state = initialState, action) {
  switch (action.type) {

    case FETCH_POSTS_STARTED:
      return {
        ...state,
        loading: true
      };
    case FETCH_POSTS_SUCCESS:
      return {
        ...state,
        loading: false,
        error: null,
        posts: action.payload.posts
      };
    case FETCH_POSTS_FAILURE:
      return {
        ...state,
        loading: false,
        error: action.payload.error
      };
    default:
      return state;
  }
}

userComponents.js includes:

import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchUsers } from "../actions/action.js";


class Users extends Component {
  componentDidMount() {
    this.props.fetchUsers();
  }

  render() {

    const { users, loading, error } = this.props;

    return (
      <div>
        {loading && <div>LOADING...</div>}
        {error && <div>{error}</div>}
        <ul>
          {users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      </div>
    );
  }
}

const mapStateToProps = state => {
  const { users, loading, error } = state.users;
  return {
    users,
    loading,
    error
  };
};

export default connect(
  mapStateToProps,
  {
    fetchUsers
  }
)(Users);

postComponents.js includes:

import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchPosts } from "../actions/action.js";

class Posts extends Component {
  componentDidMount() {
    this.props.fetchPosts();
  }

  render() {

    const { posts, loading, error } = this.props;

    return (
      <div>
        {loading && <div>LOADING...</div>}
        {error && <div>{error}</div>}
        <ul>
          {posts.map(post => (
            <li key={post.id}>{post.title}</li>
          ))}
        </ul>
      </div>
    );
  }
}

const mapStateToProps = state => {
  const { posts, loading, error } = state.posts;
  return {
    posts,
    loading,
    error
  };
};

export default connect(
  mapStateToProps,
  {
    fetchPosts
  }
)(Posts);

Upvotes: 0

Views: 237

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42208

The problem is your action name constants. You copy-and-pasted from one to the other and forgot to change the names!

export const FETCH_POSTS_STARTED = 'FETCH_POSTS_STARTED';
export const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS';
export const FETCH_POSTS_FAILURE = 'FETCH_POSTS_FAILURE';
export const FETCH_USERS_STARTED = 'FETCH_POSTS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_POSTS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_POSTS_FAILURE';

Since FETCH_USERS_SUCCESS is actually the same as FETCH_POSTS_SUCCESS, one type's fetch result is improperly updating the other type's state.

case FETCH_USERS_SUCCESS:
    return {
        ...state,
        loading: false,
        error: null,
        users: action.payload.users
    };

Specifically, the error comes about because of the above case. When we set users to action.payload.users on a action which is actually of type 'FETCH_POSTS_SUCCESS', the value of action.payload.users is undefined. So now state.users.users is undefined instead of an array, and you get a fatal error when trying to call .map() on it.

Upvotes: 2

Related Questions