Returning a dispatch with promises in action creators of react redux

KnowledgeSeeker picture KnowledgeSeeker · Feb 13, 2019 · Viewed 8.7k times · Source

I have an actionCreators in my /actions/authenticate.jsto seperate the logic of the actions dispatched by redux and the component of react.

Here is my authenticate.js which is my actionCreators

export function login(email, password) { // Fake authentication function
    return async dispatch => {
        dispatch(loginRequest()); // dispatch a login request to update the state
        try {
            if (email.trim() === "[email protected]" && password === "123456") { //If the email and password matches
                const session = { token: "abc1234", email: email, username: "test123" } // Create a fake token for authentication
                await AsyncStorage.setItem(DATA_SESSION, JSON.stringify(session)) // Stringinfy the session data and store it
                setTimeout(() => { // Add a delay for faking a asynchronous request
                    dispatch(loginSuccess(session)) // Dispatch a successful sign in after 1.5 seconds
                    return Promise.resolve()
                }, 1500)
            } else { // Otherwise display an error to the user
                setTimeout(() => { // Dispatch an error state
                    dispatch(loginFailed("Incorrect email or password"))
                }, 1500)
            }
        } catch (err) { // When something goes wrong
            console.log(err)
            dispatch(loginFailed("Something went wrong"));
            return Promise.reject()
        }
    };
} // login

Then I import that in my someComponent.js to import that actionCreator and bind it using bindActionCreators.

Something like this below:

import { bindActionCreators } from "redux";
import * as authActions from "../actions/authenticate";
import { connect } from "react-redux";

Then I connect that action to my component which is the Login.js

export default connect(
    state => ({ state: state.authenticate }),
    dispatch => ({
        actions: bindActionCreators(authActions, dispatch)
    })
)(Login);

So I can invoke the function in that actionCreator directly in the Login.js

Something like this below:

onPress={() => {                                 
   this.props.actions.login(this.state.email, this.state.password)
}}

But what I want to happen is this function will dispatch an redux action and also return a Promise if that is possible?

Something like this:

onPress={() => {                                 
       this.props.actions.login(this.state.email, this.state.password)
       .then(() => this.props.navigation.navigate('AuthScreen'))
    }}

What I want to happen is when I try to sign in. Dispatch those asynchronous redux thunk actions and as well return a promise. If is been resolved or not so I can redirect or navigate to the correct screen.

Appreciate if someone could help. Thanks in advance.

Answer

Maciek Wawro picture Maciek Wawro · Feb 14, 2019

Your first approach was mostly correct. dispatch(thunkAction()) (or in your case this.props.actions.login() returns whatever thunkAction() returns, so it does return Promise in case it's async.

The problem is what that Promise resolves to, which is whatever you return from the async function. In your case, you don't wait for setTimeout and just return void regardless of whether credentials were correct or not.

So, in terms of async functions you'd need something like

export function login(email, password) { // Fake authentication function
    return async dispatch => {
        dispatch(loginRequest()); // dispatch a login request to update the state
        try {
            if (email.trim() === "[email protected]" && password === "123456") { //If the email and password matches
                const session = { token: "abc1234", email: email, username: "test123" } // Create a fake token for authentication
                await AsyncStorage.setItem(DATA_SESSION, JSON.stringify(session)) // Stringinfy the session data and store it
                // Add a delay for faking a asynchronous
                await new Promise((resolve, reject) => setTimeout(() => resolve(), 1500));
                dispatch(loginSuccess(session));
                return Promise.resolve(true);
            } else { // Otherwise display an error to the user
                await new Promise((resolve, reject) => setTimeout(() => resolve(), 1500));
                dispatch(loginFailed("Incorrect email or password"))
                return Promise.resolve(false);
            }
        } catch (err) { // When something goes wrong
            console.log(err)
            dispatch(loginFailed("Something went wrong"));
            return Promise.reject()
        }
    };
} // login

That way, your async function resolves to true/false that you can use in your component:

onPress={() => {                                 
       this.props.actions.login(this.state.email, this.state.password)
       .then((login_succeeded) => this.props.navigation.navigate('AuthScreen'))
    }}

You can also return dispatch(loginSuccess(session)); (as long as it's thunk that returns it and not the setTimeout handler), in which case loginSuccess(session) is what .then() will get in your onPress hook.