yourfavorite
yourfavorite

Reputation: 517

Redux: Waiting for state changes before doing something else

In a hypothetical app, a user can create a book and by default that book has 3 pages. Once the book and pages are added, I want to do some other things with the book and pages. Books and Pages are two separate reducers in my redux store and also two tables in my database. My current flow is this:

  1. User creates new book.
  2. ADD_BOOK_REQUEST action is called
  3. ADD_PAGES_REQUEST action is called
  4. Book is added to database which fires ADD_BOOK_SUCCESS
  5. Pages are added to database which fires ADD_PAGES_SUCCESS

As mentioned above, after Steps 4 and 5 get executed, I'd like to do some other things with the book, but I'm not sure the best approach for this. Just to get it running for now, currently I have a setTimeout function that runs checking for the new book and 3 pages with that bookId. If book and 3 pages are in the store, do other things, if not, run the timeout again. I feel very certain this is not the best way to do this. :smile:

Upvotes: 0

Views: 4643

Answers (2)

jsplaine
jsplaine

Reputation: 621

You have a few options.

1) In your React Component, you could connect to the piece of state that is updated by your reducer when ADD_PAGES_SUCCESS. In componentWillReceiveProps you could check for that connected piece of state, and dispatch the action you want to be done after step (5) from within your Component.

...not great because componentWillReceiveProps can get hairy fast and should be avoided when possible.

2) Use redux-thunk or redux-saga:

With Redux-saga, in your ACTION handlers (sagas), you can take ACTIONS and do things like conditionally make API calls and yield (wait) for the server response, at which point you could trigger (put) more ACTIONs or make more API calls or do anything else you want. This doesn't replace your reducers nor change your redux state.

Example:

// worker Saga: will be fired on ADD_PAGES_SUCCESS actions

function* doStuffAfterAddPages(action) {
  while (true) {
    yield take(ADD_PAGES_SUCCESS);

    try {
      let moreData;
      const result = yield call(api.doSomethingElse);

      if (result.isExpected) {
        moreData = yield call(api.doOtherStuff);
      } else {
        moreData = yield call(api.doOtherStuff);
      }

      yield put(OTHER_STUFF_COMPLETE, { data: moreData });
    } catch (error) {
      yield put(OTHER_STUFF_FAILED, { error: error.message);
    }
  }
}

Upvotes: 0

markerikson
markerikson

Reputation: 67567

Gosha Arinich describes one possible solution in his post Detecting state changes with Redux-Saga.

In that post, he suggests writing a waitFor function that would return when a provided selector finally returns true:

function* waitFor(selector) {
  if (yield select(selector)) return; // (1)

  while (true) {
    yield take('*'); // (1a)
    if (yield select(selector)) return; // (1b)
  }
}

It could be used like this:

function* someSaga() {
  yield call(waitFor, state => getCurrentUser(state) != null);
  // the user is logged in by now
}

Another possible solution would be a simpler custom middleware that does that checking after actions are dispatched.

Upvotes: 8

Related Questions