Redux-thunk - dispatch is not a function - reactjs

I'm having trouble with redux-thunk. It's saying dispatch is not a function inside my action creator, I tried consoling the returned arguments and there is none.
Here goes the code:
Action
export function signUp(data) {
return dispatch => {
console.log(dispatch)
if (data.email === 'email#server.com') {
dispatch(signIn(data, () => {
if (data.type === '2') {
browserHistory.push('/settings/profile')
} else {
browserHistory.push('/')
}
}))
} else {
return {
type: ActionTypes.USER_SIGN_UP__ERROR
}
}
}
}`
mapActionsToProps
const mapActionsToProps = dispatch => ({
signUp (data) {
console.log(dispatch)
dispatch(userActions.signUp(data))
}
})
By the way, you can see I consoled the dispatch function inside the mapActionsToProps, and it is returning as it was supposed to:
function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
}

Dispatch is not a function, because it's not passed from action creator.
Besides, you should not dispatch any action inside your mapActionsToProps. You just need to bind them to be accessible by connected component.
Your mapActionsToProps
const mapActionsToProps = (dispatch) => {
return {
asyncAction: bindActionCreators(asyncAction, dispatch),
}
}
const Container = connect(mapStateToProps, mapActionsToProps)(Component);
Async action
export const asyncAction = (email) => {
return (dispatch, getState) => {
const state = getState();
dispatch(StartAsync());
return fetch(`${apiUrl}/endpoint?email=${email}`, {
method: 'GET'
})
.then(response => response.json())
.then((result) => dispatch(finishedAsync(result)),
(error) => dispatch(failedAsync(error)))
.catch(e => {
console.log('error:', e);
});
};
};
Then, in your connected component, you can dispatch this action from props.

Related

Set value in useState after redux dispatch

My axios transaction is all done in the redux actions so that I can re-use the function. The issue is that, I need to fetch the data first which is done by redux and then re-assign the value in a state, but the data cannot be populated in the state. Below is how my code looks like.
Setting.js
...
import { getUserDetail } from './redux/actions/settingActions';
export default function Setting() {
const dispatch = useDispatch()
const { user } = useSelector(state => state.settingReducer)
const [userDetail, setUserDetail] = useState()
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user) // I want to set the user here
}, [])
...
}
settingActions.js
export const getUserDetail = () => (dispatch, getState) => {
axios.get('url-goes-here')
.then(res => {
dispatch({
type: SET_USER_DETAIL,
payload: { res.data }
})
})
.catch(error => {
throw error;
})
}
settingReducer
function initialState() {
return {
...
user: {}
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_USER_DETAIL:
return {
...state,
user: payload
}
default:
return state
}
}
My purpose of doing this is because I want to do some user details update but I want it to be done within the same file.
put user and dispatch as dependency in useEffect
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user)
}, [user,dispatch])

How can I wait until I get the dispatch result in React

consider the following code :
const onSubmit = (data) => {
dispatch(Actions.updatedUser(data))
navigation.navigate('xxxx')
}
When I call Submit function we want to wait until finishing dispatch then navigate , Here How can I do that ?
This is my action :
export const updatedUser = (model) => {
return dispatch => {
api
.patch("/xxx")
.then(response => { return response.data['data'] })
.then(result => {
dispatch({ type: Actions.AUTH_UPDATE_USER, payload: result })
})
.catch(error => { })
}
}
my reducer :
const initState = {
userInfo: undefined
}
export default (state = initState, action) => {
switch (action.type) {
case Actions.AUTH_UPDATE_USER:
return { ...state, userInfo: action.payload }
default:
return state;
}
}
Here's what you can do: from your action, you can return a promise which resolves only when the dispatch is completed. Something like this:
export const updatedUser = (model) => {
return dispatch => {
return new Promise((resolve, reject) => {
api
.patch("/xxx")
.then(response => { return response.data['data'] })
.then(result => {
dispatch({ type: Actions.AUTH_UPDATE_USER, payload: result })
resolve() // <<<< this!
})
.catch(error => { reject() })
})
}
}
Now, in your component code, you can either do .then or async/await based on your preference. Here's how it would look with then:
const onSubmit = (data) => {
dispatch(Actions.updatedUser(data)).then(() => {
navigation.navigate('xxxx')
})
}
Here's a sandbox for an example

Block sending requests twice react-redux (hooks)

I have some hook for fetching countries array from api :
import { useSelector, useDispatch } from 'react-redux'
const useCountries = () => {
const dispatch = useDispatch()
const { countries } = useSelector(data => data)
useEffect(() => {
if (!countries.length) {
dispatch(getCountries())
}
}, [countries])
return {
countries
}
}
I have some components which aren't on the same level of DOM , and aren't related to each other
const FirstComponent = () => {
const {countries} = useCountries()
// some using of countries array
}
const SecondComponent = () => {
const {countries} = useCountries()
// some using of countries array
}
When page loading 2 of these components, are executed, and 2 actions are dispatched. Because countries aren't selected yet in first and in the second component it calls API request. My question is, how can I prevent sending the second request if some request was sent. Make some rule for sending request only once, if try to send the second one, then block it. Or what is the correct solution or approach in my case?
Also, I've tried a case like this :
const {
external: { response, isInitial }
} = useSelector(data => data)
useEffect(() => {
if (!response && isInitial) {
dispatch(setIsInitial(false))
fetch()
.then(res => res.json())
.then(data => {
setResponse(data)
})
}
}, [dispatch, response, isInitial])
Here is my reducer :
const initialState = {
response: null,
isInitial: true
}
export default function external(state = initialState, action) {
switch (action.type) {
case types.SET_RESPONSE:
return {
...state,
response: action.payload.data
}
case types.SET_INITIAL:
return {
...state,
isInitial: action.payload.data
}
default:
return state
}
}
But it sends 2 requests
You can modify your useCountries as below:
const useCountries = () => {
const dispatch = useDispatch()
const countries = useSelector(state => state.countries);
const isFetching = useSelector(state => state.isFetching);
useEffect(() => {
if (!countries.length && !isFetching) {
dispatch(getCountries())
}
}, [countries, isFetching])
return {
countries
}
}
You would need to flip the isFetching flag in your getCountries function and flip it again when the api has resolved.
------------------EDIT after additional information-----------------------
You would need to chain your dispatch calls. This is to ensure that flag is set before you make the api call.
To achieve that you can make dispatch thenable in the following way.
dispatch returns either of two:
For sync action (like dispatch ({type: 'ACTION'}) it will return
action object ({type: 'ACTION'} in my example)
For thunk actions (action creators which return functions) it returns
the same result returned from action creator.
This is the first case.
const syncAction = (arg1, arg2) => {
return (dispatch, getState) => {
return Promise.resolve(arg1 + arg2);
}
}
This would be the second case.
const asyncAction = (arg1, arg2) => {
return (dispatch, getState) => {
return fetch(/* some request */)
.then(response => dispatch({ type: "RESPONSE_RECEIVED", payload: response }));
};
};
Now armed with two above, you can do the following:
dispatch(syncAction(...args)).then(() => {
dispatch(asyncAction())
});
In your case syncAction - isInitial and asyncAction - getCountries().
Addind dispatch to useEffect dependencies array whould solve the issue.
const dispatch = useDispatch()
const { countries } = useSelector(data => data)
useEffect(() => {
if (!countries.length) {
dispatch(getCountries())
}
}, [countries, dispatch])

React Redux -possible to have a call back in dispatch function

Guys i am having some trouble or quite doubtful.
am having one component and one reducer.
Reducer.js
import {
ASSET_POPUP_GET_ENDPOINT,
} from 'apiCollection';
import { performGet } from 'services/rest-service/rest-service';
export const GET_ASSETS_LIST = 'stories/GET_ASSETS_LIST';
const initialState = {
imgGroup: [],
isLoading: false,
};
const modalUploadReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ASSETS_LIST: {
return {
...state,
ImageJson:action.payload.imageGroup,
};
}
case GET_ASSETS_LIST_ERROR: {
return {
...state,
isLoading:false,
};
}
default:
return state;
}
};
export const getModalClose = () => (dispatch) => {
dispatch({ type: CLOSE_MODAL });
}
export const getListActionDispactcher = () => (dispatch) => {
performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
export default modalUploadReducer;
and my component look like
it do have mapStateToProps and mapDispatchToProps
and one of the function
const mapDispatchToProps = dispatch => ({
getCollection: () => dispatch(getListActionDispactcher()),
});
addDocumentClick = () =>{
this.props.getAssetsCollection();
}
and is it possible to have some setState/manipulation of response after api response got from reducer in the component
based on the response i need to do some changes in addDocumentClick.
Means something like this
addDocumentClick = () =>{
this.props.getAssetsCollection().then(...based on response;
}
The correct way for solving this is setting a global loading flag and in your componentDidUpdate() method, checking for the value to determine that the action has just succeeded. You already seem to have the isLoading flag. Just set it when the action's dispatched, and unset it after it succeeds/fails. And in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.isLoading && !this.props.isLoading) {
// do something
}
}
Of course, you need to connect() your loading flag to your component to achieve this.
If all you care about is whether the assets list has changed, you can simply check for the change of that prop in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.ImageJson !== this.props.ImageJson) {
// do something
}
}
Another solution is sending a callback to your action dispatcher, which makes your code more tightly coupled and I don't recommend, but it does work too. So, when you connect(), you can:
getCollection: (onSuccess) => dispatch(getListActionDispactcher(onSuccess)),
In your action dispatcher:
export const getListActionDispactcher = (onSuccess) => (dispatch) => {
// ...once API finished/failed
onSuccess(someData);
}
Finally, in your component:
this.props.getCollection((result) => {
console.log('succeeded!', result);
// hide modal, etc..
}
You are using redux-thunk, and calling thunk will return a promise which will resolve in whatever you return in your thunk. Therefore, all you need to do is to add return value to getListActionDispactcher
export const getListActionDispactcher = () => (dispatch) => {
// return this promise
return performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
// return whatever you want from promise
return payload
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
.
addDocumentClick = () => {
this.props.getAssetsCollection().then(payload => console.log(payload))
}
You should, however, look for ways to avoid this pattern to have your components decoupled from actions as much as possible for the sake of modularity

Redux - how to call an action and wait until it is resolved

I'm using react native + redux + redux-thunk
I do not have much experience with redux and react native
I'm calling an action inside my component.
this.props.checkClient(cliente);
if(this.props.clienteIsValid){
...
}
and within that action there is a call to an api that takes a few seconds.
export const checkClient = (cliente) => {
return dispatch => {
axios.get(`${API_HOST}/api/checkclient`, header).then(response => {
dispatch({type: CHECK_CLIENT, payload: response.data }); //valid or invalid
}).catch((error) => { });
}
}
My question is how can I delay the return of the action until the api response is completed? I need the api response to know if the client is valid or invalid. That is, I need the action to be resolved and then verify that the client is valid or invalid.
You can return a promise from the action, so that the call becomes thenable:
// Action
export const checkClient = (cliente) => {
return dispatch => {
// Return the promise
return axios.get(...).then(res => {
...
// Return something
return true;
}).catch((error) => { });
}
}
class MyComponent extends React.Component {
// Example
componentDidMount() {
this.props.checkClient(cliente)
.then(result => {
// The checkClient call is now done!
console.log(`success: ${result}`);
// Do something
})
}
}
// Connect and bind the action creators
export default connect(null, { checkClient })(MyComponent);
This might be out of scope of the question, but if you like you can use async await instead of then to handle your promise:
async componentDidMount() {
try {
const result = await this.props.checkClient(cliente);
// The checkClient call is now done!
console.log(`success: ${result}`)
// Do something
} catch (err) {
...
}
}
This does the same thing.
I don't understand the problem, but maybe this could help
export const checkClient = (cliente) => {
return dispatch => {
dispatch({type: CHECK_CLIENT_PENDING });
axios.get(`${API_HOST}/api/checkclient`, header).then(response => {
dispatch({type: CHECK_CLIENT, payload: response.data }); //valid or invalid
}).catch((error) => { });
}
}
...
this.props.checkClient(cliente);
if(this.props.clienteIsPending){
...
}
if(this.props.clienteIsValid){
...
}
I have written a full code if there is still confusion. The promise should work for a sequence of asynchronous redux action calls
Actions
export const buyBread = (args) => {
return dispatch => {
return new Promise((resolve, reject) => {
dispatch({type: BUY_BREAD_LOADING });
// or any other dispatch event
// your long running function
dispatch({type: BUY_BREAD_SUCCESS, data: 'I bought the bread'});
// or any other dispatch event
// finish the promise event
resolve();
// or reject it
reject();
});
}
export const eatBread = (args) => {
return dispatch => {
return new Promise((resolve, reject) => {
dispatch({type: EAT_BREAD_LOADING });
// or any other dispatch event
// your long running function
dispatch({type: EAT_BREAD_SUCCESS, data: 'I ate the bread'});
// or any other dispatch event
// finish the promise event
resolve();
// or reject it
reject();
});
}
Reducer
const initialState = {}
export const actionReducer = (state = initialState, payload) => {
switch (payload.type) {
case BUY_BREAD_LOADING:
return { loading: true };
case BUY_BREAD_SUCCESS:
return { loading: false, data: payload.data };
case EAT_BREAD_LOADING:
return { loading: true };
case EAT_BREAD_SUCCESS:
return { loading: false, data: payload.data };
}
Component class
import React, {Component} from 'react';
class MyComponent extends Component {
render() {
return (
<div>
<button onClick={()=>{
this.props.buyBread().then(result =>
this.props.eatBread();
// to get some value in result pass argument in resolve() function
);
}}>I am hungry. Feed me</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
actionReducer: state.actionReducer,
});
const actionCreators = {
buyBread: buyBread,
eatBread: eatBread
};
export default connect(mapStateToProps, actionCreators)(MyComponent));

Resources