Can't dispatch action in class component - reactjs

// action
export const getEvents = () => async (dispatch) => {
try {
dispatch({ type: GET_EVENTS_REQUEST })
const data = await axios.get('http://localhost:5000/api/schedule').then((response) => response.data)
dispatch({ type: GET_EVENTS_SUCCESS, payload: data })
} catch (error) {
dispatch({
type: GET_EVENTS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
// reducer
export const getEventsReducer = (state = { event: [] }, action) => {
switch (action.type) {
case GET_EVENTS_REQUEST:
return { loading: true }
case GET_EVENTS_SUCCESS:
return { loading: false, event: action.payload }
case GET_EVENTS_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
// and this is how I'm trying to call my action:
import { getEvents } from '../../redux/actions/calendarActions'
class Calendar extends React.PureComponent {
componentDidMount() {
const { dispatch } = this.props
console.log(dispatch(getEvents()))
}
}
export default connect()(Calendar)
// component is much bigger, I only added relevant parts
Up until my reducer, if I console.log my data, it is correct, as well as in my redux dev tools tab: an array with a few entries. But when console.logging in my Calendar component, it returns a promise, with undefined result:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
What am I doing wrong?

Normally you want to have access to either the dispatch or the store of Redux within a component. you already have the dispatch function within the component, but if you need access to Redux state inside it:
first you need to define such function, which makes the redux store available in the component.
const mapStateToProps = (state) => ({
state: state // a "state" prop is available in the component which points to redux state,
})
or you can customize it if you only need certain properties of Redux state:
const mapStateToProps = (state) => ({
state: state.event //
})
and change the connect function like this:
connect(mapStateToProps)(Calendar)

Related

redux to dispatch response and a return message

In my action i am dispatching the type and the payload but what if i also want the res.status and a return JSON message to be included into my props. How would i do so in my action and reducer?
action
export const fetchUserPosts = () => (dispatch) => {
fetch(`${currentPort}/user/recipes`,
{
withCredentials: true,
credentials: 'include',
})
.then((res) => {
if (res.status !== 401) return res.json().then((data) => data);
return { message: { msgBody: 'UnAuthorized' }, msgError: true };
})
.then((posts) => dispatch({
type: FETCH_USER_POSTS,
payload: posts,
}));
};
reducer
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_USER_POSTS:
return {
...state,
fetchUsersPosts: action.payload,
};
default:
return state;
}
}
You can combine multiple items into a single payload. I would create different actions for success and error. Using the same action complicates the reducers logic. It's also easier to work with async/await then with nested promises.
This is a working example that uses SpaceX open API:
const FETCH_USER_POSTS_SUCCESS = 'FETCH_USER_POSTS_SUCCESS'
const FETCH_USER_POSTS_FAILED = 'FETCH_USER_POSTS_FAILURE'
const fetchPostSuccessAction = (payload) => ({
type: 'FETCH_USER_POSTS_SUCCESS',
payload,
})
const fetchPostFailureAction = (payload) => ({
type: 'FETCH_USER_POSTS_FAILURE',
payload,
})
const fetchUserPosts = () => async dispatch => {
const res = await fetch('https://api.spacexdata.com/v3/launches/latest');
if (res.status !== 401) {
const { ships: posts } = await res.json();
dispatch(fetchPostSuccessAction({
posts,
status: res.status,
}))
} else {
dispatch(fetchPostFailureAction({
message: { msgBody: 'UnAuthorized' },
}))
}
};
fetchUserPosts()(console.log)
The reducer can handle the object by destructuring it, and the properties to the new state in any way you need. You can also change other properties, for example changing errMsg to true or false according to the action's type:
export default function (state = initialState, { type, payload }) {
switch (type) {
case FETCH_USER_POSTS_SUCCESS: {
const { posts, status } = payload;
return {
...state,
status,
fetchUsersPosts: posts,
msgError: false,
message: null
};
}
case FETCH_USER_POSTS_FAILURE: {
const { message } = payload;
return {
...state,
status: 401,
fetchUsersPosts: null,
msgError: true,
message
};
}
default:
return state;
}
}
If I am following correctly you are using this action inside of a component to send a fetch. You don't have access to the components props with the reducer. You can send the http request in the component and use that to store the response in the state. Or use connect from 'react-redux' package to map the redux store to access the fetch result.
import { connect } from 'react-redux'
const component = props => {
//to access redux state in component use props.myprop
return <div>{props.myprops.title}</div>
}
const mapStateToProps = state => {
return{
myprop: state.fetchUsersPosts
}
}
export default connect(mapStateToProps)(component)
If this was what you were looking for you can learn more at https://react-redux.js.org/api/connect

React Redux - Loading state too slow - how to solve it

I'm trying to create a loading state for my Redux but it looks to "slow" to get updated.
First action fetchDB => setLoading: true => once over setLoading: false
Second action fetchCat => doesn't have the time to fire it that crashes
Really simple:
set loading action:
export const setLoading = () => {
return async (dispatch) => {
await dispatch({ type: SET_LOADING }); // no payload by default goes to true
};
};
set loading reducer:
import {
FETCH_DB,
SET_LOADING,
} from "../types"
const initalState = {
db: [],
loading: false,
}
export default (state = initalState, action) => {
switch (action.type) {
// this like the other cases sets loading to FALSE
case FETCH_DB:
return {
...state,
db: action.payload,
current: null,
loading: false,
}
case FETCH_CAT_FOOD:
return {
...state,
food: action.payload,
loading: false,
}
case FETCH_CAT_DESIGN:
return {
...state,
design: action.payload,
loading: false,
}
case SET_LOADING:
return {
...state,
loading: true,
}
default:
return state
}
}
then action I use that creates the problem:
export const fetchCat = kindof => {
return async dispatch => {
dispatch(setLoading()) // looks like that it doesn't get fired
const response = await axios
.get(`http://localhost:5000/api/categories/${kindof}`)
.then(results => results.data)
try {
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
and then the file (a custom component) that creates the problem.
It crashes cause categories.map is undefined.
It doesn't find loading: true so the loader doesn't stop.
import React, { useState, useEffect, Fragment } from "react"
import { Spinner } from "react-bootstrap"
import { connect, useDispatch, useSelector } from "react-redux"
import CatItem from "./CatItem" // custom component
import { fetchCat, setLoading } from "../../../store/actions/appActions"
const MapCat = ({ kindof, loading, categories }) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchCat(kindof)) // gives the category I want to fetch
// eslint-disable-next-line
}, [categories])
if (!loading) {
return (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)
} else {
return (
<Fragment>
<div>
{categories.map(item => (
<CatItem item={item} />
))}
</div>
</Fragment>
)
}
}
const mapStateToProps = (state, kindof) =>
({
loading: state.appDb.loading,
categories: state.appDb[kindof],
})
export default connect(mapStateToProps, { fetchCat, setLoading })(MapCat)
I think that it is supposed to work like this:
loading: false (by default) => true => time to fetch => false
But doesn't look like working. Any idea?
Firstly setLoading needs to return a plain object with type and payload
export const setLoading = () => ({ type: SET_LOADING });
In fetchCat the then is not required. Also async await for dispatch is not required.
export const fetchCat = (kindof) => {
return (dispatch) => {
dispatch(setLoading()); //<---this should now be ok.
const response = await axios.get(`http://localhost:5000/api/categories/${kindof}`)
// .then((results) => results.data); //<----- not required as you are using await
try {
dispatch({ type: `FETCH_CAT_${kindof}`, payload: response.data }); //<--- use response.data ...also async/await for dispatch is not rquired.
} catch (error) {
console.log("await error", error);
}
};
};
The 2nd arg of mapStateToProps is ownProps which is an object
const mapStateToProps = (state, ownProps) =>
({
loading: state.appDb.loading,
categories: state.appDb[ownProps.kindof],
})
You have quite a bit different way of calling dispatch. Let me list them out
dispatch(fetchCat(kindof)) // gives the category I want to fetch
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
You can see, await or not basically is the way you use async operation. However dispatch takes type and payload to function, which means you have to make sure what you send to dispatch is with the right object. Of course Redux does accept custom format via plugins, so maybe if you throw it a async as input, the reducer might understand it as well?
Please double check each dispatch first, for example, write a function that only dispatch one type of action. Only after you make each call working, don't move to assemble them together into a bundled call.

react props comes blank on first transaction

I am using redux promise middleware. I am trying to pass the value in Propsx to state. Props comes empty in useEffect. How can I transfer the contents of the props to state. Props value comes next.
action:
export function fetchBasket() {
return dispatch => {
dispatch({
type: 'GET_BASKET',
payload: axios.get('url', {
})
.then(response => response.data)
});
};
}
reducer:
const initialState = {
fetching: false,
error: {},
basket: []
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.GET_BASKET_PENDING:
return {
fetching: true
};
case types.GET_BASKET_FULFILLED:
return {
...state,
fetching: false,
basket: payload.result,
};
case types.GET_BASKET_REJECTED:
return {
fetching: false,
error: payload.result
};
default:
return state;
}
};
use in Component
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
[enter link description here][1]If you want to have values in your first run(Mount). fetch here ==> useLayoutEffect and this will gives the values in useEffect()[]. [uselayouteffect]: https://reactjs.org/docs/hooks-reference.html#uselayouteffect
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
Your props will update only in the next event loop cycle, to use react hooks data updation inside useEffect you need to useReducer https://reactjs.org/docs/hooks-reference.html#usereducer

Unable to get updated state after call multiple dispatch

I'm using redux with React to manage states but when I called two dispatch function from one action creator, it's return state from the first dispatch but unable to get updated state after another dispatch call.
I've tried to call dispatch from different reducers and tried to call after API call.
Here are my actions.
export const setLoader = (loader) => dispatch => {
dispatch({ type: SET_LOADER, payload: loader });
};
export const fetchCategory = (setLoader) => async dispatch => {
setLoader(true);
try {
const instance = axios.create();
instance.defaults.headers.common['Authorization'] = AUTHORIZATION_TOKEN;
const response = await instance.get(API_PATHS.SERVICE_CATEGORY_API);
dispatch({ type: FETCH_CATEGORY, payload: response.data });
} catch (e) {
setLoader(false);
}
};
Here i defined reducers:
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_CATEGORY:
return { ...state, categoryList: action.payload };
case SET_LOADER:
return { ...state, isLoading: action.payload };
default:
return state;
}
};
Here my component connected with redux:
const mapStateToProps = state => {
return ({
categoryList: state.locator.categoryList
});
}
export default connect(
mapStateToProps,
{ fetchCategory, setLoader }
)(ServiceLocator);
I expect the output to return updated categoryList, but the actual it returns a blank array.
You are performing an asynchronous task in your action creator, which Redux can't handle without a middleware. I recommend using the middleware redux-thunk. This will allow you to perform asynchronous actions in your action creators and dispatch multiple times.
Hope this helps!
UPDATE:
If you have the redux-think middleware installed and added to Redux (per your comment), then next I would look at setLoader() - it looks like that function is curried and I don't think you want it to be. I would remove the setLoader() step and dispatch that action directly from fetchCategory():
export const fetchCategory = () => async dispatch => {
dispatch({ type: SET_LOADER, payload: true });
try {
const instance = axios.create();
instance.defaults.headers.common['Authorization'] = AUTHORIZATION_TOKEN;
const response = await instance.get(API_PATHS.SERVICE_CATEGORY_API);
dispatch({ type: FETCH_CATEGORY, payload: response.data });
} catch (e) {
dispatch({ type: SET_LOADER, payload: false });
}
};

Component structure to handle Async Action with Redux-thunk ?

After a bit of trial and error I finally manage to get my action creator working properly and passing the data I wanted into my redux store. Until now I've been dispatching it "manually" like this store.dispatch(fetchTest()); but It would be great if could use these data into a component.
So here is my action creator :
export const fetchTest = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
return axios.get('http://localhost:3000/authors')
.then(data => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching:false,
data: data
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
Here is my reducer :
const initialState = {data:null,isFetching: false,error:null};
export const ThunkData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
So far everything is working properly when using store.dispatch(fetchTest());.
Based on this example I tried to build the following component :
class asyncL extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchTest(this.props.thunkData)
// got an error here : "fetchTest is not a function"
}
render() {
if (this.props.isFetching) {
return console.log("fetching!")
}else if (this.props.error) {
return <div>ERROR {this.props.error}</div>
}else {
return <p>{ this.props.data }</p>
}
}
}
const mapStateToProps = (state) => {
return {
isFetching: state.ThunkData.isFetching,
data: state.ThunkData.data.data,
error: state.ThunkData.error,
};
};
const AsyncList = connect(mapStateToProps)(asyncL);
export default AsyncList
It doesn't work, I have an error on the componentWillMount() and probably somewhere else.
Also my data structure is kind of weird. To actually get to the data array I have to do state.ThunkData.data.data. The first data object is full of useless stuff like request, headers, etc...
So how should I write this component so I can at least passed the Async data into a console.log.
Thanks.
You need to mapDispatchToProps as well.
import { fetchTest } from './myFetchActionFileHere';
import { bindActionCreators } from 'redux';
function mapDispatchToProps(dispatch) {
return {
fetchTest: bindActionCreators(fetchTest, dispatch)
};
}
const AsyncList = connect(mapStateToProps, mapDispatchToProps)(asyncL);
export default AsyncList
documentation link: http://redux.js.org/docs/api/bindActionCreators.html

Resources