Mike Rifgin
Mike Rifgin

Reputation: 10767

Wait for a redux action to finish dispatching

I have the following action creator:

export function scrolltoNextItem(item) {
  return (dispatch, getState) => {
    dispatch(appendItem(Item));
    dispatch(
      scrollToNextIndex(
        getState().items.length - 1
      )
    )
  }
}

Problem is that scrollToNextItem runs before appendItem has finished and the scroll position ends up being incorrect. I can prove this is the case by adding a setTimeout to make the execution of the script wait for the next tick before running scrollToNextItem:

export function scrolltoNextItem(item) {
  return (dispatch, getState) => {
    dispatch(appendItem(Item));
    setTimeout(() => {
      dispatch(
        scrollToNextIndex(
          getState().items.length - 1
        )
      )
    }, 0);
  }
}

How can I wait for the appendItem action to finish? In standard react land I would just use the setState callback:

this.setState({something: 'some thing'}, () => {
  console.log('something is set');
});

But dispatch doesn't provide any callback functionality.

Upvotes: 50

Views: 97078

Answers (2)

do7
do7

Reputation: 49

This can be resolved by using redux-saga, the code will be executed in synchronous why even if we have a call to an api

ref https://redux-saga.js.org/docs/api/

redux-saga code :

import {put, call, takeLatest} from 'redux-saga/effects';
import api from './api/definition/path';
import {appendItem , doSomeStuff, scrollToNextIndex} from './functions'
import SAGA_STORE_CONST from './saga/actions/constants'

export function* appendNewItem({action}) {
  try {
    // first execution : this start the scroll and put it into redux state
    yield put({
      type: SAGA_STORE_CONST.SCROLL_BEGIN,
    });
    // second execution : this is an asynchronous call to an api to get some data
    const elmtName = yield call(api.getElementName, {params: action.data});
    // third execution : this is a synchronous call to a normal function
    yield call(doSomeStuff, {params: action.data});
    // fourth execution : here we can append our Items
    yield call(appendItem, action.Item);
    // fifth execution : scrollToNextIndex
    yield call(scrollToNextIndex, action.getState().items.length - 1);
    // last execution : when scroll ends
    yield put({
      type: SAGA_STORE_CONST.SCROLL_END,
    });
  } catch (error) {
    ...
  }
}

export default function* itemsSaga() {
  yield takeLatest(
    SAGA_STORE_CONST.APPEND_NEW_ITEM,
    appendNewItem,
  );
}

Note:

    yield call // are used to call synchronous or asynchronous functions
    yield put // to dispatch normal redux actions

The call to the redux-saga action can be fired by :

    dispatch({
      type: SAGA_STORE_CONST.APPEND_NEW_ITEM,
      action: {data, Item, getState},
    });

Upvotes: 1

Alberto Centelles
Alberto Centelles

Reputation: 1253

You can always wrap appendItem into a promise and pass dispatch as an argument to it

const appendItem = (item, dispatch) => new Promise((resolve, reject) => {
  // do anything here
  dispatch(<your-action>);
  resolve();
}

Then you can call it like this from scrolltoNextItem

export function scrolltoNextItem(item) {
  return (dispatch, getState) => {
    appendItem(Item, dispatch).then(() => {
      dispatch(
        scrollToNextIndex(
          getState().items.length - 1
        )
      )
    })
  }
}

Upvotes: 10

Related Questions