Redux state from actions and reducers does not show in mapStateToProps - javascript

I have an actions file called menu that uses axios to get JSON data from a backend API. I then have reducers which are called in a MainMenu.js file which gets the data. All of the data is shown in the Redux devtools. However I am only able to access the data I have in intitialState. There should be an array of three items but it only shows the first one in intitialState. The data from the reducers does not show up when I try to console.log it. So obviously I would not be able to render it to the DOM.
actions/menu.js
import { ADD_PRODUCT, GET_PRODUCTS, GET_PRODUCT } from './types';
import axios from 'axios';
export const getProducts = () => (dispatch) => {
axios
.get('http://localhost:8080/menu')
.then((response) => {
// console.log(response);
const data = response.data;
dispatch({
type: GET_PRODUCTS,
payload: { data },
});
})
.catch((err) => {
console.log(err);
});
};
export const getProduct = (product) => (dispatch) => {
axios
.get(`http://localhost:8080/menu/${product}`)
.then((response) => {
// console.log(response);
const data = response.data;
dispatch({
type: GET_PRODUCT,
payload: { data },
});
})
.catch((err) => {
console.log(err);
});
};
reducers/index.js
import { combineReducers } from 'redux';
import menu from './menu';
export default combineReducers({ menu });
reducers/menu.js
import { GET_PRODUCTS, GET_PRODUCT } from '../actions/types';
const intitialState = [
{
test: 'Test',
},
];
export default function (state = intitialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PRODUCTS:
return [...state, payload];
case GET_PRODUCT:
return [...state, payload];
default:
return [...state];
}
}
components/MainMenu.js
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import { getProducts, getProduct } from '../actions/menu';
const MainMenu = ({ getProducts, getProduct, menu }) => {
useEffect(() => {
getProducts();
getProduct('b4bc2e28-21a3-47ea-ba3b-6bad40b35504');
console.log(menu);
}, []);
return <Fragment></Fragment>;
};
const mapStateToProps = (state) => ({
menu: state,
});
export default connect(mapStateToProps, { getProducts, getProduct })(MainMenu);

I created a working solution using the ternary operator. First a check is made to see if all of the elements in the array have loaded. If they have not all loaded then a data not loaded div is displayed. Otherwise the data is rendered to the DOM.
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import { getProducts, getProduct } from '../actions/menu';
const MainMenu = ({ getProducts, getProduct, menu }) => {
useEffect(() => {
getProducts();
getProduct('b4bc2e28-21a3-47ea-ba3b-6bad40b35504');
}, []);
if (menu.length <= 2) {
console.log('Data Not Loaded');
} else {
console.log('Data', menu[2].data.name);
}
return (
<Fragment>
<div>{menu.length <= 2 ? <div>Data not loaded</div> : <div>{menu[2].data.name}</div>}</div>
<div>
{menu.length <= 2 ? (
<div>Data not loaded</div>
) : (
<div>
{menu[1].data.map((food) => (
<div>{food.name}</div>
))}
</div>
)}
</div>
</Fragment>
);
};
const mapStateToProps = (state) => ({
menu: state.menu,
});
export default connect(mapStateToProps, { getProducts, getProduct })(MainMenu);

Related

fetch function doesn't called with react-redux

I want to call a rest api with react-redux but my fetch doesn't called at all.
My actions:
restActions.js file:
export const customerbyidGetAction = (data) => ({ type: types.customerbyidGetAction, data });
My reducer:
restReducer.js file:
import { Map, fromJS } from 'immutable';
import * as types from '../constants/restConstants';
const initialState = {};
const initialImmutableState = fromJS(initialState);
export default function reducer(state = initialImmutableState, action) {
switch(action.type) {
case types.customerbyidGetAction:
return {
...state,
data: action.payload
}
break;
default:
// the dispatched action is not in this reducer, return the state unchanged
return state;
}
}
restApi.js file:
import {customerbyidGetAction} from '../../redux/actions/restActions'
const URL = "https://***"
export default function customerbyidGet() {
return dispatch => {
console.log("not called")
dispatch(customerbyidGetAction());
fetch(URL + 'customer/byid/Get',{
method:'POST',
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json',
'token':1234
},
body: JSON.stringify({'customerId': 1})
})
.then(res => {
const r = res.json();
return r;
})
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(customerbyidGetAction(res));
return res;
})
.catch(error => {
dispatch(customerbyidGetAction(error));
})
}
}
export default apis;
inside my Component:
import React from 'react';
import { Helmet } from 'react-helmet';
import Grid from '#material-ui/core/Grid';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { withStyles } from '#material-ui/core/styles';
import PropTypes from 'prop-types';
import {
Help, InsertDriveFile,
MonetizationOn,
Person,
PersonPin,
RemoveRedEye
} from '#material-ui/icons';
import { Link } from 'react-router-dom';
import CounterWidget from '../../../components/Counter/CounterWidget';
import colorfull from '../../../api/palette/colorfull';
import styles from '../../../components/Widget/widget-jss';
import PapperBlock from '../../../components/PapperBlock/PapperBlock';
import {customerbyidGetAction} from '../../../redux/actions/restActions';
class Panelclass extends React.Component {
componentDidMount(){
const {customerbyidGet} = this.props;
customerbyidGet()
}
render() {
const { classes } = this.props;
return (
<div>
Hi
</div>
);
}
}
Panelclass.propTypes = {
classes: PropTypes.object.isRequired,
customerbyidGet: PropTypes.func.isRequired,
};
// const mapStateToProps = state => ({
// customers: customerbyidGet(state),
// })
const mapDispatchToProps = dispatch => bindActionCreators({
customerbyidGet: customerbyidGetAction
}, dispatch)
const Panel = connect(
//mapStateToProps,
null,
mapDispatchToProps
)(Panelclass);
export default withStyles(styles)(Panel);
Your reducer expects payload property in action:
function reducer(state = initialImmutableState, action) {
switch (action.type) {
case types.customerbyidGetAction:
return {
...state,
data: action.payload, // Here
};
}
}
So, you need to have that property in action creator:
const customerbyidGetAction = (data) => ({
type: types.customerbyidGetAction,
payload: data, // Here
});
Also, you need to fix the correct action import in component:
import { customerbyidGet } from "../path-to/restApi";
const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
customerbyidGet,
},
dispatch
);
PS: Today, you should use the Redux Toolkit library which reduces lots of boilerplate code.
First of all define your rest api function like below
export default function customerbyidGet(dispatch) {
return () => {
console.log("not called")
dispatch(customerbyidGetAction());
fetch(URL + 'customer/byid/Get',{
method:'POST',
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json',
'token':1234
},
body: JSON.stringify({'customerId': 1})
})
.then(res => {
const r = res.json();
return r;
})
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(customerbyidGetAction(res));
return res;
})
.catch(error => {
dispatch(customerbyidGetAction(error));
})
}
}
Then pass it to your mapDispatchToProps function
const mapDispatchToProps = dispatch => bindActionCreators({
customerbyidGetData: customerbyidGet(dispatch)
}, dispatch)
And finally`invoke it in mapDispatchToProps
componentDidMount(){
const {customerbyidGetData} = this.props;
customerbyidGetData()
}
The other two answers are pointing out good points, but missing something very crucial: That ; there is a typo.
const {customerbyidGet} = this.props;
customerbyidGet()
should be
const {customerbyidGet} = this.props.customerbyidGet()

'map' is undefined with Redux and React

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
}
}

How to make api call with react-redux in functional components

I am trying to replace my old react app with only functional component and i'm stuck at making api call with 'axios' in redux.
This is my api call function
return new Promise((resolve, reject) => {
return axios[method.toLowerCase()](path, data)
.then(res => {
return resolve(res.data);
})
.catch(err => {
return reject(err.response.data.error);
});
});
}
this is for redux
export const loadCategories = categories => ({
type: LOAD_CATEGORIES,
categories
});
export const fetchCategories = () => {
return dispatch => {
return apiCall("get", "/api/categories/getall")
.then(function(res) {
dispatch(loadCategories(res));
})
.catch(function(err) {
addError(err.message);
});
};
};
import { LOAD_CATEGORIES } from "../actionTypes";
const category = (state = [], action) => {
switch (action.type) {
case LOAD_CATEGORIES:
return [...action.categories];
default:
return state;
}
};
export default category;
What I'm trying to do is to just execute fetchCategories() function
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchCategories } from "../store/actions/categories";
import { Link } from "react-router-dom";
const Nav = ({ categories }) => {
useEffect(() => {
fetchCategories();
}, []);
return <div/>;
};
function mapStateToProps(state) {
return {
categories: state.categories
};
}
export default connect(mapStateToProps, { fetchCategories })(Nav);
but it does not making api call at all...
It seems that you are connecting the action creator fetchCategories but you are actually calling the unconnected version.
Try calling fetchCategories from the received props:
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchCategories } from "../store/actions/categories";
import { Link } from "react-router-dom";
// Note that we are calling now the connected version of fetchCategories
const Nav = ({ categories, fetchCategories }) => {
useEffect(() => {
fetchCategories();
}, []);
return <div/>;
};
function mapStateToProps(state) {
return {
categories: state.categories
};
}
export default connect(mapStateToProps, { fetchCategories })(Nav);
Because you need fetchCategories from props, not from ../store/actions/categories
const Nav = ({ categories, fetchCategories }) => {
useEffect(() => {
fetchCategories();
}, []);
return <div/>;
};
I would suggest to go with redux-thunk or redux-saga to handle async flows in react.
Checkout how to handle asyncflows using redux-thunk -> https://github.com/reduxjs/redux-thunk.
Checkout how to handle asyncflows using redux-saga -> https://redux-saga.js.org/

Redux Dev tools updating. Console not updating with state changes or data

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 getting TypeError: this.props.function is not a function while fetching from an api

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.

Categories

Resources