Reputation: 10767
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
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
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