Noranda Brown
Noranda Brown

Reputation: 99

Redux Thunk synchronous actions are not synchronous

I'm using React/Redux for an app I'm building and I'm trying to implement an action to log out the user. My app has artifacts that can be edited. I want my app to first save the artifact, then log out the user, which will clear the store. I've implemented this as follows using Redux Thunk:

export function logoutUser(artifact) {
  return (dispatch) => {
    dispatch(saveArtifact(artifact))
      .then(() => {
        dispatch(requestLogout());
        return axios.get(`${API_BASE}/auth/logout`)
          .then(() => dispatch(logoutSuccess()))
          .catch(error => dispatch(logoutFailure(error)));
      });
  };
}

I would expect this to save the artifact and only when it's saved, log the user out. My save action calls another action itself which loads the artifacts after they're saved, but that shouldn't matter. It should return a promise and only then move on to logging out the user.

However, this is not what is happening. For some reason, I am getting multiple calls to save the artifact (verified they are coming from this function) and then the calls happen asynchronously, so that loading the artifacts is last, and thus my redux store is not getting cleared upon logout.

Would love if someone could provide some insight into why this is happening and how I can fix it.

Additional info:

I am clearing the redux store in my root reducer as follows:

const rootReducer = (state, action) => {
  let newState = state;
  if (action.type === types.LOGOUT_SUCCESS) {
    newState = undefined;
  }

  return appReducer(newState, action);
};

I am saving the artifacts as follows:

function sendSaveArtifactRequest(artifact) {
  if (artifact._id == null) {
    const artifactWithoutId = _.omit(artifact, ['_id']);
    return axios.post(`${API_BASE}/artifacts`, artifactWithoutId)
      .then(result => result.data);
  }

  return axios.put(`${API_BASE}/artifacts`, artifact).then(() => artifact);
}

export function saveArtifact(artifact) {
  return (dispatch) => {
    dispatch(requestSaveArtifact());

    return sendSaveArtifactRequest(artifact)
      .then(data => dispatch(saveArtifactSuccess(data)))
      .catch(error => dispatch(saveArtifactFailure(error)))
      .then(() => dispatch(loadArtifacts()));
  };
}

I am loading artifacts as follows:

function sendArtifactsRequest() {
  return new Promise((resolve, reject) => {
    axios.get(`${API_BASE}/artifacts`)
      .then(result => resolve(result.data))
      .catch(error => reject(error));
  });
}

export function loadArtifacts() {
  return (dispatch) => {
    dispatch(requestArtifacts());

    return sendArtifactsRequest()
      .then(data => dispatch(loadArtifactsSuccess(data)))
      .catch(error => dispatch(loadArtifactsFailure(error)));
  };
}

Upvotes: 2

Views: 2747

Answers (1)

Sakhi Mansoor
Sakhi Mansoor

Reputation: 8102

I have found the issue, there are multiple chained promises in your saveArtifact action. What is happening in your case, after sendSaveArtifactRequest promise resolution you dispatch requestLogout and upon it's success you clear redux state. But on the other side there are still pending promises you're not waiting for their resolution in logoutUser action. When you clear redux store you pending promise gets resolved and the update redux state again. You can do two things here:

  • use Promise.all().then() while dispatching saveArtifact then dispatch logout action.
  • You can Logout user in loadArtifacts action but I'm not sure exact use case, maybe this doesn't fit with your app requirement. but it will always called when artifacts are loaded.

Most importantly, please integrate Redux DevTools in your app so you can track in which sequence actions are dispatched.

in your store.js

import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

import rootReducer from './reducers';

const enhancer = compose(
  applyMiddleware(thunk),
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
);

export default createStore(rootReducer, enhancer);

`

Upvotes: 1

Related Questions