1

I'm having a problem that seems to be due to an async call. I have an action that makes an API call and pushes to a Dashboard page. That API call also updates state.account.id based on the response it gives back:

const submitLogin = e => {
        e.preventDefault();
        props.loginAndGetAccount(credentials);
        props.history.push('/protected');
        e.target.reset();
    }

loginAndGetAccount is coming from this action:

export const loginAndGetAccount = credentials => dispatch => {
    dispatch({ type: GET_ACCOUNT_START })
    axios
        .post('https://foodtrucktrackr.herokuapp.com/api/auth/login/operators', credentials)
        .then(res => {
            console.log(res);
            dispatch({ type: GET_ACCOUNT_SUCCESS, payload: res.data.id })
            localStorage.setItem("token", res.data.token)
        })
        .catch(err => console.log(err));
}

On the Dashboard page, I have useEffect set up to make another API call dynamically based on the value held in state.account.id. However, it seems the first API call is pushing to the Dashboard page before the response comes back and updates state.account.id. Therefore, when the second API call is made there, it's passing state.account.id to that dynamic API call as undefined, which, of course, results in a failed call. How can I resolve this? Here's what's happening:

const Dashboard = props => {
    const [accountInfo, setAccountInfo] = useState({});

    useEffect(() => {
            console.log(props.accountId);
            axiosWithAuth()
                .get(`/operator/${props.accountId}`)
                .then(res => {
                    console.log(res);
                })
                .catch(err => console.log(err));
    }, [])

    return (
        <div>
            <h1>This is the Dashboard component</h1>
        </div>
    )
}

const mapStateToProps = state => {
    return {
        accountId: state.account.id
    }
}

export default connect(mapStateToProps, {})(Dashboard);

enter image description here

Jevon Cochran
  • 1,565
  • 2
  • 12
  • 23

1 Answers1

2

The root of the problem is that you are making a request here, but not

export const loginAndGetAccount = credentials => dispatch => {
    dispatch({ type: GET_ACCOUNT_START })
    axios
        .post('https://foodtrucktrackr.herokuapp.com/api/auth/login/operators', credentials)
        .then(res => {
            console.log(res);
            dispatch({ type: GET_ACCOUNT_SUCCESS, payload: res.data.id })
            localStorage.setItem("token", res.data.token)
        })
        .catch(err => console.log(err));
}

waiting for it to complete here before you navigate to the next page

const submitLogin = e => {
        e.preventDefault();
        props.loginAndGetAccount(credentials);
        props.history.push('/protected');
        e.target.reset();
    }

the quickest way to fix this is to returnt the promise from loginAndGetAccount and then props.history.push in the resolution of that promise...

like this:

export const loginAndGetAccount = credentials => dispatch => {
    dispatch({ type: GET_ACCOUNT_START })
    // return the promise here
    return axios
        .post('https://foodtrucktrackr.herokuapp.com/api/auth/login/operators', credentials)
        .then(res => {
            console.log(res);
            dispatch({ type: GET_ACCOUNT_SUCCESS, payload: res.data.id })
            localStorage.setItem("token", res.data.token)
        })
        .catch(err => console.log(err));
}

...


const submitLogin = e => {
    e.preventDefault();
    props.loginAndGetAccount(credentials)
        .then(() => {
            // so that you can push to history when it resolves (the request completes)
            props.history.push('/protected');
            e.target.reset();
        }
        .catch(e => {
            // handle the error here with some hot logic
        })
}
Joshua Wootonn
  • 1,041
  • 7
  • 9
  • This worked, only that the result fn can't be in the resolution of the promise, has to be outside. Also, I realized that I could just put props.accountId inside the dependency array in the useEffect hook and then use an if statement to check for props.accountId before running the fn inside the useEffect hook. Both ways work. – Jevon Cochran Mar 06 '20 at 05:26
  • 1
    True both work for the happy path, but what about when the login request fails and you already routed them to the dashboard route? Are you going to route them back and display an error message on the login page? I think it would be better to catch the returned promise in the submitLogin before routing them to another location. So that you can display the error text there and not allow them to proceed without a successful login – Joshua Wootonn Mar 06 '20 at 05:34