redux-logic
redux-logic copied to clipboard
Make `dispatch` promisified
For most of my projects, I have to make multiple dispatch calls in process and I normally write like this:
const logic = createLogic({
type: FOO,
async process({ getState, action }, dispatch, done) {
try {
const json = await api.get(...).getBody();
dispatch(setAuthToken(json.token));
dispatch(fetchUser());
} finally {
done();
}
}
});
But this (very occasionally) fails, because fetchUser relies on a valid auth token and sometimes it was called before setAuthToken was completed, despite being dispatched order.
What about making dispatch a function that returns a promise? So that I can use await in front of it. Like this:
await dispatch(setAuthToken(json.token));
dispatch(fetchUser());
I can do monkeypatching for redux's store.dispatch function, but it looks like redux-logic's dispatch is different.
FYI, here's my implementation for redux's store.dispatch patching.
store.js
const store = createStore(...);
const { dispatch } = store;
store.dispatch = action =>
new Promise(resolve => {
EE.once(`redux-action/${action.type}`, resolve);
dispatch(action);
});
general-logics.js
const promiseStyleDispatchLogic = createLogic({
type: '*',
process({ action }) {
EE.emit(`redux-action/${action.type}`);
}
});
EE is an instance of EventEmitter (https://github.com/primus/eventemitter3)
I replaced the above monkeypatching with a redux middleware since monkeypatching looks a little hacky. Here's my simple middleware:
function promisifyDispatchMiddleware() {
return next => action =>
new Promise(resolve => {
if (action.type) {
EE.once(`redux-action/${action.type}`, resolve);
}
next(action);
});
}
@artificis is the action for setAuthToken not synchronous? I'm wondering why it would need a promise.
setAuthToken action is supposed to run synchronously in theory and mostly it does. However, very rarely, fetchUser action is called before setAuthToken is completed, thus resulting an error (because fetchUser passes an auth token into http request header but auth token resolves to null).
This might sound a stupid case, but this is not the only case I face. There are other cases. For instance, I need to update intercom parameter if user logs in, and I want this action to be dispatched only after fetchUser action is complete. I know I can solve this by moving code that updates intercom parameter into fetchUser logic, but this breaks DRY principle if I need to update intercom data in other place also. I want multiple action dispatches to be composable, not monolithic.
@artificis is the action for setAuthToken not synchronous? I'm wondering why it would need a promise.
Good day,
I have an effect that executes an async function, but after that function is executed my effect does not dispatch the next sync action. Any guidance on what I could be doing wrong?
The code sample below:
effects: [
{
type: [`FORM_TRANSMIT`],
gaurd: fn((t) => (ctx, allow, reject) => {
const payload = t.omit(['status'], t.at('action.payload', ctx))
if (t.allOf([t.notEmpty(payload), t.notNil(payload)])) {
allow(ctx.action)
}
reject()
}),
options: { dispatchReturn: false },
effect: fn((t) => async ({ getState, action }, dispatch, done) => {
const state = getState()
const viewActions = initViews.state.viewActions[action.payload.active]
const [err, res] = await a.of(
viewActions.transmit({
state: state[viewKey].views[action.payload.active],
next: action.payload,
})
)
dispatch(
ctx.mutators.formTransmitComplete({
viewKey,
payload: t.merge(state[viewKey], { user: res.user }),
})
)
done()
}),
},
{
type: `MODAL_CHANGE`,
effect: fn((t) => (context, dispatch, done) => {
const state = context.getState()
return (state.home.modal.formData = context.action.payload)
}),
},
]
My create logic function looks as follows:
createLogic({
name: view.name,
type: t.isType(type, 'array')
? t.map((actionType) => `${view.name}/${actionType}`, type)
: `${view.name}/${type}`,
validate: gaurd,
processOptions: {
dispatchReturn: t.atOr(false, 'dispatchReturn', options),
},
process: effect,
...extra,
})
When I return another action from the effect, only then does it work.