I know I shouldn't be trying to dispatch thunks from sagas, it goes against what redux-saga tries to do. But I'm working in a fairly large app and most of the code is made with thunks, we are migrating by bits and need to dispatch a thunk from inside a saga. The thunk can't be changed because it is used in other parts (a thunk that returns a promise), so it would break many things.
configureStore:
const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(thunk, sagaMiddleware))
);
Saga:
// Saga (is called from a takeEvery)
function* watchWarehouseChange(action) {
const companyId = yield select(Auth.id);
// We use cookies here instead of localStorage so that we persist
// it even when the user logs out. (localStorage clears on logout)
yield call(Cookies.set, `warehouse${companyId}`, action.warehouse);
// I want to dispatch a thunk here
yield put.resolve(syncItems);
// put(syncItems) doesn't work either
}
Thunk:
export function syncItems() {
console.log('first!');
return dispatch => {
console.log('second!');
return dispatch(fetchFromBackend()).then(
items => itemsDB.emptyAndFill(items)
)
}
}
Whenever syncItems()
is executed, only first!
logs. second!
never happens.
PS: I don't get any errors or warnings.
We apply the thunk middleware, then we can dispatch thunks. This means that we can call dispatch with other thunks within a thunk, so we can compose them easily. Since Redux Thunk 2.1.0, we can use the withExtraArgument function to add an extra argument to the action that we return the function that we want to use with dispatch.
Since dispatching a thunk returns the thunk return value, you could write a thunk that accepts a selector, and immediately calls the selector with the state and returns the result. This can be useful in a React component, where you have access to dispatch but not getState.
If you pass a function into dispatch, the thunk middleware sees that it's a function instead of an action object, intercepts it, and calls that function with (dispatch, getState) as its arguments If it's a normal action object (or anything else), it's forwarded to the next middleware in the chain
To dispatch async actions into our store, we have to apply the thunk middleware by writing: const store = createStore (joke, applyMiddleware (thunk)); to apply the middleware. Then in App, we call dispatch with the function returned from the fetchJoke passed inside. fetchJoke is an async action, which is also known as a thunk.
You're using syncItems
wrong. The key is that the function returned by syncItems
needs to get passed to dispatch
, not syncItems
itself. The correct usage would be:
yield put(syncItems());
I showed some visual comparisons of how values are passed into dispatch
in my blog post Idiomatic Redux: Why use action creators? (based on an example gist I put together). Here's the examples:
// approach 1: define action object in the component
this.props.dispatch({
type : "EDIT_ITEM_ATTRIBUTES",
payload : {
item : {itemID, itemType},
newAttributes : newValue,
}
});
// approach 2: use an action creator function
const actionObject = editItemAttributes(itemID, itemType, newAttributes);
this.props.dispatch(actionObject);
// approach 3: directly pass result of action creator to dispatch
this.props.dispatch(editItemAttributes(itemID, itemType, newAttributes));
// parallel approach 1: dispatching a thunk action creator
const innerThunkFunction1 = (dispatch, getState) => {
// do useful stuff with dispatch and getState
};
this.props.dispatch(innerThunkFunction1);
// parallel approach 2: use a thunk action creator to define the function
const innerThunkFunction = someThunkActionCreator(a, b, c);
this.props.dispatch(innerThunkFunction);
// parallel approach 3: dispatch thunk directly without temp variable
this.props.dispatch(someThunkActionCreator(a, b, c));
In your case, just substitute yield put
for this.props.dispatch
, since you're dispatching from a saga instead of a connected component.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With