Callback function for this.props.dispatch - reactjs

I am calling a dispatch function as below
this.props
.dispatch({
type: LOGIN_REQUESTED,
payload: {
username,
password,
},
})
i want to dispatch another action on successfully execution of this action
i tried with then function but getting error that this.props.dispatch(...).then is not a function

Since you're using redux saga, you can have a generator to watch for LOGIN_SUCCESS.
Dispatch this action after the API had successfully responded.
takeLatest(LOGIN_REQUESTED, watchForLogin)
takeLatest(LOGIN_SUCCESSFUL, watchForLoginSuccess)
watchForLogin(action) {
doLogin('/some/url')
.then(response => dispatch(loginSuccess(response))) // dispatch login success
.catch(error => dispatch(loginFailed(error))) // dispatch error
}
watchForLoginSuccess(action) {
doSomethingAfterLogin()
}

Related

Understanding useDispatch and dispatch in React-Redux

I am trying to understand the code that my past colleague has written.
From what I understand, useDispatch takes in an object that contains the action type and payload, which will be compared to all the reducers and the state will be changed accordingly. However, in the below code, he passes an entire function instead of an object. My question is why can useDispatch still function without passing in an object, as the function does not return anything.
Below is the useDispatch function and the action function.
const dispatch = useDispatch();
const handleSubmit = (e: any): void => {
dispatch(login("email", "password"));
};
export const login =
(email: string, password: string) =>
async (dispatch: Dispatch<LoginActions>) => {
try {
// Update the store
dispatch({
type: USER_ACTIONS.USER_LOGIN_REQUEST,
});
const data = await loginRequest({ email, password });
dispatch({
type: USER_ACTIONS.USER_LOGIN_SUCCESS,
payload: data,
});
} catch (error: any) {
dispatch({
type: USER_ACTIONS.USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
For redux itself, you can only dispatch action which is a plain JS object(or an action creator returns an action, this action is a plain JS object must have a type field.). But with various middlewares, you can dispatch various things such as a thunk, plain JS object action, etc. The middleware will transform the action to plain JS object action in the end.
When using the redux-thunk middleware, you can dispatch a thunk like this:
const thunkFunction = (dispatch, getState) => {
// logic here that can dispatch actions or read state
}
store.dispatch(thunkFunction)
The login thunk action creator does return a thunk.
A thunk action creator is a function that may have some arguments, and returns a new thunk function. See Writing Thunks
Another example, redux-promise-middleware,
Given a single action with an async payload, the middleware transforms the action to separate pending action and a separate fulfilled/rejected action, representing the states of the async action.
You can dispatch an action with async payload:
const response = dispatch({
type: 'FIRST',
payload: new Promise(...),
});

Is it possible to await for dispatch is finish

I have 2 actions, and I call from the first action to the second action .... and I need to wait until the second action is finished and only then continue with the action.
// first action
export const getDataFromAdmin = () => {
return async (dispatch, getState) => {
dispatch(getDeviceLocation());
console.log('only after getDeviceLocation is finsih');
AdminRepository.getDataFromAdminAndEnums(dispatch)
.then(adminData => {
//some code
})
.catch(error => {
console.log(`Splash Error = ${error.message}`);
});
};
};
//second action
export const getDeviceLocation = () => {
return async dispatch => {
dispatch({ type: actionsType.GET_DEVICE_LOCATION });
LocationManager.getCurrentPosition()
.then(location => {
dispatch({ type: actionsType.GET_DEVICE_LOCATION_SUCCESS });
})
.catch(error => {
dispatch({ type: actionsType.GET_DEVICE_LOCATION_ERROR, message: error.message });
});
};
};
No, it's not possible because dispatching an action is something like triggering action and action just invoke either middleware or reducer that's it. The action doesn't wait to complete the reducer or middleware. It just invokes and finishes his job.
Ok, so in the end I make async await by pass dispatch as a parmeter to the function.
Yes you can await dispatch, but only if you are using redux-thunk middleware.
dispatch is a synchronous action by default. redux-thunk middleware allows you to dispatch actions which are a function of dispatch, like the actions in your question, in addition to the standard { type: 'SOME_NAME' } action objects. redux-thunk also makes it so that dispatching these "thunk" actions is asyncronous. This allows you to use aysnc/await or Promise.then() when you call dispatch(myThunkAction());
async function someFunction() {
await dispatch(myThunkAction());
doSomethingElse();
}
dispatch(myThunkAction()).then(() => {
doSomethingElse();
});
Here is an example from the redux-thunk documentation:
// In fact I can write action creators that dispatch
// actions and async actions from other action creators,
// and I can build my control flow with Promises.
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
// You don’t have to return Promises, but it’s a handy convention
// so the caller can always call .then() on async dispatch result.
return Promise.resolve()
}
// We can dispatch both plain object actions and other thunks,
// which lets us compose the asynchronous actions in a single flow.
return dispatch(makeASandwichWithSecretSauce('My Grandma'))
.then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
)
.then(() => dispatch(makeASandwichWithSecretSauce('Our kids')))
.then(() =>
dispatch(
getState().myMoney > 42
? withdrawMoney(42)
: apologize('Me', 'The Sandwich Shop')
)
)
}
}
You can view the complete code in the redux-thunk docs section on Composition to see how they define the makeASandwichWithSecretSauce thunk.
The resolved value of the Promise will be whatever you return in your myThunkAction function. Typically thunks will call dispatch and will not return anything, so this feature is rarely used.
Any return value from the inner function will be available as the return value of dispatch itself. This is convenient for orchestrating an asynchronous control flow with thunk action creators dispatching each other and returning Promises to wait for each other’s completion. (source)
async function someFunction() {
const result = await dispatch(myThunkAction());
doSomethingWithResult(result);
}
dispatch(myThunkAction()).then((result) => {
doSomethingWithResult(result);
});

React Redux - Waiting for async api call to finish before next action is dispatched

I am working on a web app that uses React + Redux, with a backend using Django (DRF). I am using axios to send in my API request, which is asynchronous. The issue I am facing right now is that the actions dispatched do not wait for the API call to finish before the next action is dispatched. Below is my code
const mapDispatchToProps = dispatch => ({
success: id => {
dispatch(fetchSalesProject(id));
dispatch(createMessage('Requirement successfully updated!'))
}
})
fetchSalesProject action (axiosInstance is just a custom modification of axios call, the functionality is the same)
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
createMessage action
export const createMessage = (message) => {
console.log('message')
return {
type: CREATE_MESSAGE,
message: message,
};
};
When calling this.props.success (refer to mapDispatchToProps), the message is displayed before the api call response data is received (evident by the fact that console.log('message') runs before console.log('fetched data'))
I would want the data to be fetched from the api call before i run the createMessage action, is there any advise on how to accomplish that? I am new to React and especially Redux, so hope that you guys can point me in the right direction on how to accomplish that.
Also, can I check whether it is wrong to have a dispatch in the mapDispatchToProps, and also a dispatch within the action (refer to fetchSalesProject action). Would it cause any issues with performance or is it frowned upon to do so? Please advise me as I am quite confused with Redux.
Thanks all for reading through, all help is appreciated :-)
while you are dispatching from UI, you just sending an object towards reducer which in his turn will modify the state at the store and in the end of the process will re-render components that refer to props that changed. At the moment you are dispatching the first action, there is nothing that tells the component that it should wait before sending the next object to the reducer
So you have 2 options,
the first is at UI itself use componentDidUpdate or useEffect for run the second action after the first action reduced
componentDidUpdate(prevProps) {
if (prevProps.salesProject != this.props.salesProject)
dispatch(createMessage('Requirement successfully updated!'))
}
while I assume that dispatch(fetchSalesProject(id)); modify salesProject prop
Another way to do that, and in case you actually fine with that message and salesProject will gonna get together to the reducer, is to dispatch them in one action
export const fetchSalesProjectAndMessage = (id, message) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT_AND_MESSAGE,
payload: { data: res.data, message }
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
and at reducer payload.data either payload.message will refer to desired info
There is a better way of doing this that does not force you combine two action creators. When your thunk action creator returns a promise then you can wait for it. Your current code did not return the axios promise but if you do return it you can do the following:\
const mapDispatchToProps = (dispatch) => ({
success: (id) => {
dispatch(fetchSalesProject(id)).then(() =>
dispatch(//wait for fetchSalesProject to finish
createMessage('Requirement successfully updated!')
)
);
},
});
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action');
return axiosInstance //you did not return anything here
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data');
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => {
dispatch(
returnErrors(err.response.data, err.response.status)
);
//return rejected promise here
return Promise.reject(err);
});
};

Dispatch async redux action from non-react component with thunk middleware

I am building an react / redux webapp where I am using a service to make all my API calls. Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store.
The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions.
What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user.
I am already using react-thunk (https://github.com/gaearon/redux-thunk) but I dont see how I can injectdispatch` into non-react components.
What do I need to do? Or is it generally a bad practice to dispatch actions outside from react components?
This is what my api.services.ts looks like right now:
... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';
export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
let promise = new Promise((resolve, reject) => {
const headers = {
"Content-Type": "application/json",
"Authorization": getFromStorage('auth_token')
};
const options = {
body: data ? JSON.stringify(data) : null,
method,
headers
};
fetch(url, options).then((response) => {
const statusAsString = response.status.toString();
if (statusAsString.substr(0, 1) !== '2') {
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
store.dispatch(UserActions.logout());
}
reject();
} else {
saveToStorage('auth_token', response.headers.get('X-TOKEN'));
resolve({
data: response.body,
headers: response.headers
});
}
})
});
return promise;
};
Thanks!
If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument:
const doSomeStuff = dispatch => {
fetch(…)
.then(res => res.json())
.then(json => dispatch({
type: 'dostuffsuccess',
payload: { json }
}))
.catch(err => dispatch({
type: 'dostufferr',
payload: { err }
}))
}
Another option is to use middleware for remote stuff. This works the way, that middle can test the type of an action and then transform it into on or multiple others. have a look here, it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests.
maybe you can try to use middleware to catch the error and dispatch the logout action,
but in that case, the problem is you have to dispatch error in action creator which need to check the log status
api: throw the error
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
throw new Error('401')
}
action creator: catch error from api, and dispatch error action
fetchSometing(ur)
.then(...)
.catch(err => dispatch({
type: fetchSometingError,
err: err
})
middleware: catch the error with 401 message, and dispatch logout action
const authMiddleware = (store) => (next) => (action) => {
if (action.error.message === '401') {
store.dispatch(UserActions.logout())
}
}
You should have your api call be completely independent from redux. It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. Something like
if (statusAsString === '401') {
reject({ logout: true })
}
reject({ logout: false });
Then in your action creator code you would do:
function fetchWithAuthAction(url, method, data) {
return function (dispatch) {
return fetchWithAuth(url, method, data).then(
({ data, headers }) => dispatch(fetchedData(data, headers)),
({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
} else {
dispatch(fetchedDataFailed());
}
);
};
}
Edit:
If you don't want to write the error handling code everywhere, you could create a helper:
function logoutOnError(promise, dispatch) {
return promise.catch(({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
}
})
}
Then you could just use it in your action creators:
function fetchUsers() {
return function (dispatch) {
return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
}
}
You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the
// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status
if (response.status === '401') {
store.dispatch(UserActions.logout())
}

Redux Thunk Callback after dispatching an action?

So in my React component, I have this:
this.props.updateAlertCallback('error', ERROR_MESSAGE)
My updateAlertCallback action is:
export const updateAlert = (alert, message) => {
return {
type: 'UPDATE_ALERT',
alert,
message
}
}
export const updateAlertCallback = (alert, message) => {
return dispatch => {
return dispatch(updateAlert(alert, message)).then(() => {
console.log('Done!');
});
}
}
I'm getting the following error: Uncaught TypeError: dispatch(...).then is not a function
What's the proper way to log something after updateAlert is done running?
With redux-thunk, you can make action return a promise:
export const updateAlert = (alert, message) => (dispatch, getState) => {
dispatch ({
type: 'UPDATE_ALERT',
alert,
message
});
return Promise.resolve(getState());
// or just Promise.resolve();
now you can call updateAlert(xx, xx).then(newState => {.....});
function showAlert(message) {
return {
type: SHOW_ALERT,
message
};
}
function hideAlert(message) {
return {
type: HIDE_ALERT,
};
}
function flashAlert(message) {
return (dispatch) => {
dispatch(showAlert(message));
setTimeout(() => {
dispatch(hideAlert());
}, 5000);
}
}
You'll need redux-thunk for this to work. You can then use this.props.flashAlert('Oh noes!!!!') with the proper mapStateToProps. Also needed are reducers and react components.
Fading isn't necessarily an easy thing to do in react. I suggest you save that for later.
What the flashAlert function does is it returns a function that takes a dispatch function. This function does all kinds of fun things but not yet. First this function gets passed to redux's dispatch. This dispatch would normally throw because actions must be plain objects. But because you're using redux-thunk it will be fine. Redux-thunk will call this function and pass it the dispatch function from redux. Now the function will run, finally. First thing it does is dispatch an action that it gets by calling showAlert(). This time it's an object with a type property, which makes it a proper redux action. Presumably redux will pass this action on to our reducer which will update the state with the new message, but we don't know that for sure because the reducer was left out of this answer for brevity. Who know what code it contains. After the state was changed to show the message somehow, we do a setTimeout(). When this calls back we dispatch another action we get by calling hideAlert() using the same dispatch function we used previously. We still have it. This presumably will scrub the message from the state.
Redux will tell react to rerender the appropriate components whenever the state changes. Presumably one of those components will display or not display the message as the case may be.
Redux-thunk is your answer. In your store code change
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
);
to
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f,
applyMiddleware(thunk)
);
and you will be able to use thunks with your redux actions.
Refer to https://github.com/gaearon/redux-thunk#installation
Actions in redux are plain objects. Redux thunk allows to return functions instead of objects. These functions are executed by the thunk middleware, and ultimately the final object that reaches the store for dispatch is a plain object. An example of redux thunked action is below.
export default class AccountActions {
static login(username, password) {
return (dispatch, getStore) => {
dispatch(AccountActions.loginRequest(username));
fetch(apiUrls.signInUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
user: {
email: username,
password: password,
}
})
})
.then(response => {
return response.json().then(responseJson => {
return dispatch(AccountActions.loginResponse(username, responseJson.token, response.status));
});
})
.catch(err => {
console.error(err);
});
};
}
static loginRequest(username) {
return {
type: ActionTypes.loginRequest,
username,
};
}
static loginResponse(username, token, status) {
return {
type: ActionTypes.loginResponse,
username,
token,
status,
};
}
}

Resources