How to set the state in Redux from a component passing payload? - javascript

I can't set the state with mapDispatchToProps and pass the payload
Imports:
import React, { Component } from 'react';
import classes from './HoursWatched.mod ule.css';
import axios from 'axios';
import { connect } from 'react-redux';
import * as actionType from '../../../store/actions';
This is how I try to trigger the action:
componentDidMount() {
this.getHoursWatched()
}
getHoursWatched() {
axios.get(API_URL, header)
.then((res) => {
console.log(res)
this.props.hoursWatched(res)
})
.catch((err) => {
console.log("Error", err)
})
}
The mapDiscpatchToProps:
const mapDispatchToProps = dispatch => {
return {
hoursWatched : (res) => dispatch({ type: actionType.HOURS_WATCHED, payload: res })
}
}
Exports:
export default connect(mapStateToProps, mapDispatchToProps)(HoursWatched);
In the reducer:
const reducer = (state = initialState, action, payload) => {
switch (action.type) {
case actionTypes.HOURS_WATCHED:
const newHoursWatched = {};
console.log(payload) //This is undefined
return {
...state,
hoursWatched: newHoursWatched
};
Why I can't pass the payload? It says it's undefined

You are trying to refer to a wrong argument in the reducer. In order to access the payload that you are passing from the API response you need to use action.payload in the reducer instead of payload.

Related

React TypeError: Cannot read property 'nbaGames' of undefined

Says the nbaGames is undefined on line 7. I've done this before with other examples but this is giving me a problem. getNbaGames is never even called before the error is thrown. I'm using https://github.com/bradtraversy/devconnector_2.0/tree/master/client as the foundation of this. Thanks
NbaGames.js
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import NbaGameItem from './NbaGameItem';
import { getNbaGames } from '../../actions/game';
const NbaGames = ({ getNbaGames, nbaGame: { nbaGames } }) => {
useEffect(() => {
getNbaGames();
}, [getNbaGames]);
return (
<Fragment>
<div className="posts">
{nbaGames.map((nbaGame) => (
<NbaGameItem key={nbaGame._id} nbaGames={nbaGame} />
))}
</div>
</Fragment>
);
};
NbaGames.propTypes = {
getNbaGames: PropTypes.func.isRequired,
nbaGame: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
nbaGame: state.nbaGame
});
export default connect(mapStateToProps, { getNbaGames })(NbaGames);
game.js (reducer)
import {
GET_NBA_GAMES,
GAME_ERROR,
GET_NBA_GAME,
} from '../actions/types';
const initialState = {
nbaGames: [],
nbaGame: null,
loading: true,
error: {}
};
function gameReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_NBA_GAME:
return {
...state,
nbaGames: payload,
loading: false
};
case GET_NBA_GAME:
return {
...state,
nbaGame: payload,
loading: false
};
case GAME_ERROR:
return {
...state,
error: payload,
loading: false
};
default:
return state;
}
}
export default gameReducer;
game.js (action)
import api from '../utils/api';
import { setAlert } from './alert';
import {
GET_NBA_GAMES,
GAME_ERROR,
GET_NBA_GAME,
} from './types';
// Get posts
export const getNbaGames = () => async dispatch => {
try {
const res = await api.get('/posts');
dispatch({
type: GET_NBA_GAMES,
payload: res.data
});
} catch (err) {
dispatch({
type: GAME_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
// Get post
export const getNbaGame = id => async dispatch => {
try {
const res = await api.get(`/posts/${id}`);
dispatch({
type: GET_NBA_GAME,
payload: res.data
});
} catch (err) {
dispatch({
type: GAME_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
The problem lies in your function definition:
const NbaGames = ({ getNbaGames, nbaGame: { nbaGames } }) => {
The first time the component is rendered, the nbaGame prop will be null according to your default state (I'm not sure why your error message says it's undefined instead of null, potentially because no action has been dispatched yet, so the reducer hasn't even initialized the default state). This means that the destructured argument nbaGames cannot be accessed, because nbaGame.nbaGames is not valid if nbaGame is null.
I'm a bit confused about what the structure of your nbaGame and nbaGames objects is supposed to be, so I cannot say what the right solution would be, but generally speaking the problem could be solved in a few different ways:
Make the default value of nbaGame undefined instead of null and then use a default argument in the component definition: const NbaGames = ({ getNbaGames, nbaGame: { nbaGames } = { undefined } }) => {
Change the default value of nbaGame to be an object
Change the component definition to not destructure the argument: const NbaGames = ({ getNbaGames, nbaGame }) => {. Then access nbaGames using the optional chaining operator (nbaGame?.nbaGames).
In my index.js reducer file I have game as the state variable, so in
mapStateToProps = (state) => ({
nbaGame: state.nbaGame
});
it needs to be state.game
index.js (reducer) below
import { combineReducers } from 'redux';
import alert from './alert';
import auth from './auth';
import profile from './profile';
import game from './game';
import post from './post';
export default combineReducers({
alert,
auth,
profile,
game,
post
});

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;

redux thunk won't work - actions should be an object use custom middleware

Can anyone help me figure out what I'm doing wrong? I keep getting actions should be an object use custom middleware error. It works if I try to return like { type: 'SOMETHING' } on the fetchAdmins(), but according to the redux-thunk docs I should be able to return a function that has dispatch as params and that's what I did but maybe I missed something.
store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import allReducers from './js/reducers/index.js';
const Store = (initialState) =>
createStore(
allReducers,
initialState,
applyMiddleware(thunk)
);
export default Store;
RootAdmin.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Route } from 'react-router-dom';
import { fetchAdmins, addAdmin, deleteAdmin } from '../actions/actions.js';
#connect(
state => ({
admins: state.admins
}),
dispatch => bindActionCreators({
fetchAdmins: fetchAdmins,
addAdmin: addAdmin,
deleteAdmin: deleteAdmin
}, dispatch)
)
class RootAdmin extends Component {
// ...codes
componentDidMount() {
this.props.fetchAdmins();
}
// ...codes
}
};
export default RootAdmin;
actions.js
import axios from 'axios';
export function fetchAdmins() {
console.log('fired'); // this gets fired.
return (dispatch) => {
console.log('not fired'); // not fired.
dispatch({ type: 'FETCHING_ADMINS' });
console.log('fetching'); // won't even log
axios({
url: '/api/fetchAdmins'
})
.then(res =>
dispatch({ type: 'FETCHED_ADMINS', payload: res.data })
)
.catch(err =>
dispatch({ type: 'FAILED_FETCH_ADMINS' })
);
};
}
reducer-admins.js
export default function (state = null, action) {
const { payload } = action;
let newState = {...state};
switch (action.type) {
case 'FETCHING_ADMINS':
newState = {...payload};
newState.log += '\nfetching admins';
console.log('fetching admins');
return newState;
break;
}
return state;
}
Thank you very much!
It's not your action creator causing the issue... I believe the issue lies in your mapDispatchToProps
#connect(
state => ({
admins: state.admins
}),
dispatch => bindActionCreators({
fetchAdmins: fetchAdmins,
addAdmin: addAdmin,
deleteAdmin: deleteAdmin
}, dispatch)
)
Note that you're returning an object from the state mapping function, but in your dispatch you're returning the result of bindActionCreators which can be an object or a function...
#connect(
state => ({
admins: state.admins
}),
dispatch => ({
actions: bindActionCreators(Object.assign({}, fetchAdmins, addAdmin, deleteAdmin), dispatch)
})
)
then access your method as this.props.actions.fetchAdmins();

Not able to access response object via this.props | React/ Redux

When I try to access to the response object in my component it doesnt throw me an error but it wont either print. I do get access to the response in the component but thats it, i cant actually print something.
Actions File
import axios from 'axios';
import { FETCH_USERS, FETCH_USER } from './types';
const BASE_URL = "http://API_URL/endpoint/"
export function fetchUsers(id,first_name, last_name, dob) {
const request = axios.post(`${BASE_URL}member-search?patientId=${id}&firstName=${first_name}&lastName=${last_name}&dateOfBirth=${dob}&length=10`).then(response => { return response; })
return {
type: FETCH_USERS,
payload: request
};
}
export function fetchUser(id) {
const request = axios.get(`${BASE_URL}members/${id}/summary/demographics`).then(response => { return response; })
return{
type: FETCH_USER,
payload: request
};
}
My reducer file
import _ from 'lodash';
import {
FETCH_USERS, FETCH_USER
} from '../actions/types';
export default function(state = [], action) {
switch (action.type) {
case FETCH_USER:
return { ...state, [action.payload.data.member.id]: action.payload.data.member };
// return [ action.payload.data.member, ...state ];
case FETCH_USERS:
return _.mapKeys(action.payload.data.searchResults, 'id');
}
return state;
}
And finally my component where Im trying to render some results of the response.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchUser } from '../actions';
class PatientWrapper extends Component{
componentDidMount() {
const { id } = this.props.match.params;
this.props.fetchUser(id);
}
render(){
const { user } = this.props;
console.log('this.props response: ',user);
if(!user){
return <div>loading...</div>;
}
return(
<div>
Name: {user.firstName}
Last Name: {user.lastName}
</div>
)
}
}
function mapStateToProps({ users }, ownProps) {
// return { users };
return { user: users[ownProps.match.params.id] };
}
export default connect (mapStateToProps, { fetchUser })(PatientWrapper);
I uploaded a Screenshot img of the response : http://prntscr.com/fbs531
What is wrong with my code?
The issue is that in fetchUser action you use a Promise and return it in payload field. This promise does not contain any information you need like response data. So to fix the issue you need to dispatch action only when response is retrieved (e.g. in then success callback).
To implement it you need to pass mapDispatchToProps in the second argument in connect function for your component and pass dispatch function to your action:
function mapDispatchToProps(dispatch) {
return {
fetchUser: id => fetchUser(id, dispatch)
}
}
Then in the action just do the following
function fetchUser(id, dispatch) {
const request = axios.get(`${BASE_URL}/${id}`)
.then(response => dispatch({
type:FETCH_USER,
payload: response
}));
}
For complete example see JSFiddle

fetch should not be in the action || reducer?

I'm doing it right if I put the fetch in the componentDidMount ()? It is folly to put the fetch into the action or reducer?
Why this {this.props.data.name} does not work without setting of standard parameters for data in reducer? Without this (state = {data: { }}, action)?!
Reducer
const reducer = (state = {
data: {
}
}, action) => {
switch (action.type) {
case 'EXPERIMENT':
return {
...state,
data: action.data
}
break
default:
return state
}
}
export default reducer
Component
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Persistent extends Component {
componentDidMount () {
fetch('https://api.github.com/users/reactjs').then((response) => {
response.json().then((json) => {
this.props.dispatch({
type: 'EXPERIMENT',
data: json
})
})
})
}
render () {
return (
<div>
<ol>
<li>{this.props.data.name}</li>
<li>{this.props.data.url}</li>
</ol>
</div>
)
}
}
export default connect(
(state) => {
return {
data: state.data
}
}
)(Persistent)
Use redux-thunk middleware. It allows action creators to return functions instead of action objects. The functions are chained to do a final dispatch of an action object.
While creating the store, include the middleware as follows:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import RootReducer from '../reducers/rootReducer';
const loggerMiddleware = createLogger();
export function configureStore(initialState) {
return createStore(
RootReducer,
initialState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
));
}
An example of an action creator which uses the thunk middleware:
getCards(email, token) {
return (dispatch, getStore) => {
dispatch(CardActions.getCardsRequest(email));
fetch(apiUrls.getCardsUrl + email, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Api-Key': token,
},
})
.then(response => {
return response.json().then(responseJson => {
return dispatch(CardActions.getCardsResponse(responseJson.postcards, response.status));
});
})
.catch(err => {
console.error(err);
});
};
}
One of key constraint of redux is that reducers have to be pure functions. What it means is that it cannot have side-effects like fetching data. So in short fetch cannot go into reducer.
reducer is a function and (state = {data: { }}, action) are parameters to reducer function with the part {data: { }} being the default value for the state. Your code uses es2015 arrow functions syntax which is equivalent of plain old js:
function reducer(state = {data: { }}, action) {
switch (action.type) {
case 'EXPERIMENT':
return {
...state,
data: action.data
}
break
default:
return state
}
}
It won't work without setting default parameters because store won't have necessary objects.

Categories

Resources