Getting a weird error where 'map' is undefined. I'm not sure if my functions are firing at the wrong time and that's resulting in no data being received.
I'm adding Redux into my simple little application that just pulls data from an API and displays it. It's a list of a bunch of Heroes. Like I said before, I think that the error is coming from different times in the ansyc API call and when Redux is firing. But then again I'm a novice so any help is much appreciated.
import React, {useEffect} from 'react'
import { connect } from 'react-redux'
import { fetchHeroes } from '../actions/heroesActions'
import { Hero } from '../components/Hero'
const HeroesPage = ({ dispatch, loading, heroes, hasErrors }) => {
useEffect(() => {
dispatch(fetchHeroes())
}, [dispatch])
const renderHeroes = () => {
if (loading) return <p>Loading posts...</p>
if (hasErrors) return <p>Unable to display posts.</p>
return heroes.map(hero => <Hero key={hero.id} hero={hero} />)
}
return (
<section>
<h1>Heroes</h1>
{renderHeroes()}
</section>
)
}
// Map Redux state to React component props
const mapStateToProps = state => ({
loading: state.heroes.loading,
heroes: state.heroes.heroes,
hasErrors: state.heroes.hasErrors,
})
export default connect(mapStateToProps)(HeroesPage)
export const GET_HEROES = 'GET HEROES'
export const GET_HEROES_SUCCESS = 'GET_HEROES_SUCCESS'
export const GET_HEROES_FAILURE = 'GET_HEROES_FAILURE'
export const getHeroes = () => ({
type: GET_HEROES,
})
export const getHeroesSuccess = heroes => ({
type: GET_HEROES_SUCCESS,
payload: heroes,
})
export const getHeroesFailure = () => ({
type: GET_HEROES_FAILURE,
})
export function fetchHeroes() {
return async dispatch => {
dispatch(getHeroes())
try {
const response = await fetch('https://api.opendota.com/api/heroStats')
console.log(response)
const data = await response.json()
dispatch(getHeroesSuccess(data))
} catch (error) {
dispatch(getHeroesFailure())
}
}
}
index.js where I created the store
// External imports
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
// Local imports
import App from './App'
import rootReducer from './reducers'
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
import React, {useEffect} from 'react'
import { useSelector } from 'react-redux'
import { fetchHeroes } from '../actions/heroesActions'
import { Hero } from '../components/Hero'
const HeroesPage = () => {
const data = useSelector(state => state.heroes);
useEffect(() => {
fetchHeroes();
}, [])
const renderHeroes = () => {
if (data.loading) return <p>Loading posts...</p>
if (data.hasErrors) return <p>Unable to display posts.</p>
return data.heroes.map(hero => <Hero key={hero.id} hero={hero} />)
}
return (
<section>
<h1>Heroes</h1>
{renderHeroes()}
</section>
)
}
export default HeroesPage
action file
// import store from your createStore file and access dispatch from it
dispatch = store.dispatch
export const GET_HEROES = 'GET HEROES'
export const GET_HEROES_SUCCESS = 'GET_HEROES_SUCCESS'
export const GET_HEROES_FAILURE = 'GET_HEROES_FAILURE'
export const getHeroes = () => ({
type: GET_HEROES,
})
export const getHeroesSuccess = heroes => ({
type: GET_HEROES_SUCCESS,
payload: heroes,
})
export const getHeroesFailure = () => ({
type: GET_HEROES_FAILURE,
})
export const fetchHeroes = () => {
dispatch(getHeroes())
try {
const response = await fetch('https://api.opendota.com/api/heroStats')
console.log(response)
const data = await response.json()
dispatch(getHeroesSuccess(data))
} catch (error) {
dispatch(getHeroesFailure())
}
}
reducer file
import * as actions from '../actions/heroesActions'
export const initialState = {
heroes: [],
loading: false,
hasErrors: false,
}
export default function heroesReducer(state = initialState, action) {
switch (action.type) {
case actions.GET_HEROES:
return { ...state, loading: true }
case actions.GET_HEROES_SUCCESS:
return { heroes: action.payload, loading: false, hasErrors: false }
case actions.GET_HEROES_FAILURE:
return { ...state, loading: false, hasErrors: true }
default:
return state
}
}
Related
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);
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.
Ive been trying to do this with react hooks and the useSelector/useDispatch. What happens is, I am able to see the data and state change in the Redux DevTools however, when logging to the console, I either get an empty array or undefined. I am also not able to render the data to the screen expectedly.
Posts Component
import React, {useState, useEffect} from 'react'
import PropTypes from 'prop-types'
import {useSelector, useDispatch} from 'react-redux'
import {getPosts} from '../actions/postActions'
const Posts = props =>{
const dispatch = useDispatch();
const postData = useSelector(state=> state.items, []) || []; //memoization?
const [items, setItems] = useState(postData)
console.log(postData);
useEffect(() => {
dispatch(getPosts());
}, []);
return(
<h1>{postData[0]}</h1>
)
}
export default Posts
ACTIONS
import {GET_POSTS, NEW_POSTS} from '../actions/types'
export const getPosts =()=> dispatch =>{
//fetch
console.log('fetching')
const url = 'https://jsonplaceholder.typicode.com/posts/'
fetch(url)
.then(res => res.json())
.then(posts=> dispatch({type: GET_POSTS, payload: posts}))
}
reduxDevTools image
I think the problem is coming from this line:
const postData = useSelector(state=> state.items, []) || [];
If you want postData to initially be an array, it's best to set it as an array in your reducer.
Working example (click Posts tab to make API call):
actions/postActions.js
import api from "../utils/api";
import * as types from "../types";
export const getPosts = () => async dispatch => {
try {
dispatch({ type: types.POSTS_FETCH });
const res = await api.get("posts");
dispatch({
type: types.POSTS_SET,
payload: res.data
});
} catch (err) {
dispatch({
type: types.POSTS_FAILED_FETCH,
payload: err.toString()
});
}
};
reducers/postsReducer.js
import * as types from "../types";
const initialState = {
data: [],
error: "",
isLoading: true
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.POSTS_FETCH:
return initialState;
case types.POSTS_SET:
return {
...state,
data: payload,
error: "",
isLoading: false
};
case types.POSTS_FAILED_FETCH:
return {
...state,
error: payload,
isLoading: false
};
default:
return state;
}
};
containers/FetchPostsHooks/index.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getPosts } from "../../actions/postActions";
import Spinner from "../../components/Spinner";
import DisplayPosts from "../../components/DisplayPosts";
const Posts = () => {
const dispatch = useDispatch();
const { isLoading, data, error } = useSelector(state => state.posts, []);
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
return isLoading ? (
<Spinner />
) : error ? (
<p>{error}</p>
) : (
<DisplayPosts data={data} />
);
};
export default Posts;
I am trying to fetch data from an api, but I am getting an error: TypeError: this.props.getPeople is not a function, while everything looks good through the code, as below:
people-component.js
import React, { Component } from 'react';
import './people-component-styles.css';
import { Container, Row, Col, Card } from 'react-bootstrap';
import { connect } from 'react-redux';
import { getPeople } from '../../actions/index'
import 'react-lazy-load-image-component/src/effects/blur.css';
import 'animate.css/animate.min.css';
export class People extends Component {
componentDidMount() {
// console.log(this.props);
this.props.getPeople();
}
render() {
// console.log(this.props);
return (
<Row className='main'>
hello!
</Row>
);
}
}
const mapStateToProps = state => ({
people: state.people
})
const mapDispatchToProps = dispatch => ({
getPeople: () => dispatch(getPeople())
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(People);
actions/index.js
export const getPeople = () => {
console.log("yes");
return async dispatch => {
const response = await fetch('https://dma.com.eg/api.php?action=getPeople', {
method: 'GET'
})
const json = await response.json();
dispatch({ type: "GET_PEOPLE", payload: json });
}
}
reducers/index.js
const INITIAL_STATE = {
people: []
}
const rootReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case "GET_PEOPLE":
return ({
...state,
people: state.people.concat(action.payload)
})
default:
return state;
}
};
export default rootReducer
I was importing People component as name while it's exported as default, thanks to #Brian Thompson .. it's fixed.
i used react 16+ and redux get jsonplaceholder fake data to assign posts state but not working. can't assign the state. how can i assign json values into state using concat method. i check lifecycle methods also but can't get the answer.
Reducer
import * as actiontypes from './actions';
import axios from 'axios';
const initalstate = {
counter: 0,
posts: []
};
const reducer = (state = initalstate, action ) => {
switch (action.type) {
case actiontypes.actionFetchPost:
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
return {
...state,
posts: state.posts.concat(res.data)
}
});
break;
default :
return state;
}
};
export default reducer;
Redux reducers must be pure functions, it means they should not contain any side effects like calling api.
You need to call api in action creators using redux-thunk package.
Codesandbox
An example action creator:
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
}
};
};
And reducer file:
import {
FETCH_POSTS_STARTED,
FETCH_POSTS_SUCCESS,
FETCH_POSTS_FAILURE
} from "../actions/actionTypes";
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;
}
}
In store we use redux-thunk like this:
import { createStore, compose, applyMiddleware, combineReducers } from "redux";
import reduxThunk from "redux-thunk";
import postsReducers from "./reducers/postsReducers";
const rootReducer = combineReducers({
posts: postsReducers
});
const store = createStore(rootReducer, compose(applyMiddleware(reduxThunk)));
export default store;
Posts component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchPosts } from "./store/actions/postsActions";
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);
Index.js
import ReactDOM from "react-dom";
import store from "./store/store";
import { Provider } from "react-redux";
import Posts from "./Posts";
function App() {
return (
<div className="App">
<Posts />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
Do api call in action and return promise, use redux-thunk and redux-promise-middleware:
export const myApiCall = (args1, arg2) => async (dispatch, getState) => {
const payload = fetch({ ...config });
return dispatch({ type: 'MY_API_CALL', payload });
}
Then in reducer will have to handle two results:
MY_API_CALL_FULFILLED and MY_API_CALL_REJECTED
Please tell me why, when I call this.props.getOnEvents(), an error occurs that “getOnEvents() is not a function”, what’s wrong here and how to fix it?
I’m looking at the console.log and there is this function in the props, but when I try to call, an error flies out that it’s not a function
EventCalendar.js
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import EventCalendarTable from './EventCalendarTable';
import EventCalendarView from './EventCalendarView';
const styles = theme => ({
root: {
width: '80%',
margin: '20px auto 0'
},
});
class EventCalendar extends Component {
constructor(props) {
super(props);
this.state = {
viewEvents: 'table'
};
}
componentDidMount() {
this.props.onGetEvents();
}
changeEventsView = (value) => {
this.setState({ viewEvents: value });
}
render() {
console.log(this.props);
const { classes } = this.props;
return (
<div className={classes.root}>
<EventCalendarView changeEventsView={this.changeEventsView}/>
{
this.state.viewEvents === 'table'
? <EventCalendarTable />
: <div>test</div>
}
</div>
);
}
}
export default withStyles(styles)(EventCalendar);
EventPage/component.jsx
import React from 'react';
import EventCalendar from '../../components/EventCalendar';
import './index.scss';
function Events(props) {
return (
<React.Fragment>
<EventCalendar props={props}/>
</React.Fragment>
);
}
export default Events;
EventPage/container.js
import { connect } from 'react-redux';
import EventsPage from './component';
import { getEvents } from '../../../store/modules/Events/operations';
const mapStateToProps = ({ Events }) => ({
Events
});
const mapDispatchToProps = dispatch => ({
onGetEvents: () => {
console.log(123);
dispatch(getEvents());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(EventsPage);
Events/actions.js
import * as types from './types';
export const eventsFetch = value => ({
type: types.FETCHING_EVENTS,
payload: value
});
export const setEvents = ({ objById, arrayIds }) => ({
type: types.SET_EVENTS,
payload: {
eventById: objById,
eventsOrder: arrayIds
}
});
Events/types.js
export const FETCHING_EVENTS = 'Events/FETCHING_EVENTS';
export const SET_EVENTS = 'Events/SET_EVENTS';
Events/operation.js
import FetchClient from 'app/utils/FetchClient';
import IdsAndByIds from 'app/utils/IdsAndByIds';
import { eventsFetch, setEvents } from './actions';
export const getEvents = () => async (dispatch) => {
try {
const { data } = await FetchClient.get('/events');
dispatch(setEvents(IdsAndByIds(data)));
dispatch(eventsFetch(false));
} catch (error) {
console.log(error);
}
};
Events/reducer.js
import { createReducer } from 'store/utils';
import * as types from './types';
const usersInitState = {
fetching: true,
events: {
eventById: null,
usersOrder: null
},
error: null
};
const eventsReducer = createReducer(usersInitState)({
[types.FETCHING_EVENTS]: (state, { payload }) => ({
...state,
fetching: payload
}),
[types.SET_EVENTS]: (state, { payload }) => ({
...state,
events: {
...payload
}
})
});
export default eventsReducer;
Events/index.js
import { combineReducers } from 'redux';
import * as eventsListOperations from './operations';
import reducer from './reducers';
const EventsReducer = combineReducers({
eventsList: reducer
});
export default EventsReducer;
export { eventsListOperations };
The issue here is a minor one, Since you are connecting Events component to connect, you are receiveing the prop onGetEvents in that component, Now inside this component you are passing the props by a name props to the EventCalendar component
<EventCalendar props={props}/>
Now the props in EventCalender will contain a key called as props which wil lhave your data but you are trying to access it directly on props which is why it is undefined.
The correct way to pass the props here would be to use spread syntax like
<EventCalendar {...props}/>