admin管理员组文章数量:1291109
As must of the applications in the world, my React application needs to perform some Ajax calls to an API.
I've chosen to use Redux middlewares in order to correctly separate API fetching from my ponents.
The idea is to dispatch REQUEST
actions from my ponents. Middlewares listen for them and dispatch SUCCESS
or ERROR
actions: these last are listened by the reducers.
A lot of people here have already asked how to dispatch actions from Redux middlewares: this is NOT the topic of my question here :)
First, let me show you a simple reducer I use to write:
function currentUserReduxer(state = {}, action = {}) {
const { currentUser, error } = action.payload;
switch (action.type) {
case 'GET_CURRENT_USER_REQUEST':
return { ...state, isFetching: true, error: null };
case 'GET_CURRENT_USER_SUCCESS':
return { ...state, id: currentUser.id, isFetching: false };
case 'GET_CURRENT_USER_FAILURE':
return { ...state, isFetching: false, error };
default:
return state;
}
}
And the corresponding middleware:
() => next => async (action) => {
next(action);
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
try {
const currentUser = await api.getCurrentUser();
next(actions.getCurrentUserSuccess(currentUser));
} catch (error) {
next(actions.getCurrentUserFailure(error));
}
break;
}
default:
break;
}
};
It worked well since a long time, and then I realized it's partially wrong: my middleware does not return the value of next
, so it breaks the middleware chain and it's wrong!
Since next(action);
was the first thing I've performed in the middleware, I couldn't return it this soon, so I've moved it at the end of the middleware. I've also decided to dispatch new actions instead of using next
for them (after all, they ARE new actions, it does make sense to send them to the whole chain of middlewares). My new middleware look like this now:
store => next => async (action) => {
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
try {
const currentUser = await api.getCurrentUser();
store.dispatch(actions.getCurrentUserSuccess(currentUser));
} catch (error) {
store.dispatch(actions.getCurrentUserFailure(error));
}
break;
}
default:
break;
}
return next(action);
};
It looks great, but I have now another issue: since store.dispatch
is synchronous, next(action)
is called after. That means that my reducers receive the REQUEST
action AFTER the SUCCESS
or FAILURE
ones :(
I think one solution could be using good old promises instead of await
:
store => next => async (action) => {
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
api.getCurrentUser()
.then((currentUser) => {
store.dispatch(actions.getCurrentUserSuccess(currentUser));
})
.catch((error) => {
store.dispatch(actions.getCurrentUserFailure(error));
});
break;
}
default:
break;
}
return next(action);
};
Another idea would be to wrap the store.dispatch
with a setTimeout
:
store => next => async (action) => {
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
try {
const currentUser = await api.getCurrentUser();
setTimeout(() => store.dispatch(actions.getCurrentUserSuccess(currentUser)));
} catch (error) {
setTimeout(() => store.dispatch(actions.getCurrentUserFailure(error)));
}
break;
}
default:
break;
}
return next(action);
};
To be honest, I don't really like these two solutions, they feel so hacky...
So here is my question: how am I suppose to handle my problem? Is there a more clean way to do that?
Thanks in advance :)
As must of the applications in the world, my React application needs to perform some Ajax calls to an API.
I've chosen to use Redux middlewares in order to correctly separate API fetching from my ponents.
The idea is to dispatch REQUEST
actions from my ponents. Middlewares listen for them and dispatch SUCCESS
or ERROR
actions: these last are listened by the reducers.
A lot of people here have already asked how to dispatch actions from Redux middlewares: this is NOT the topic of my question here :)
First, let me show you a simple reducer I use to write:
function currentUserReduxer(state = {}, action = {}) {
const { currentUser, error } = action.payload;
switch (action.type) {
case 'GET_CURRENT_USER_REQUEST':
return { ...state, isFetching: true, error: null };
case 'GET_CURRENT_USER_SUCCESS':
return { ...state, id: currentUser.id, isFetching: false };
case 'GET_CURRENT_USER_FAILURE':
return { ...state, isFetching: false, error };
default:
return state;
}
}
And the corresponding middleware:
() => next => async (action) => {
next(action);
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
try {
const currentUser = await api.getCurrentUser();
next(actions.getCurrentUserSuccess(currentUser));
} catch (error) {
next(actions.getCurrentUserFailure(error));
}
break;
}
default:
break;
}
};
It worked well since a long time, and then I realized it's partially wrong: my middleware does not return the value of next
, so it breaks the middleware chain and it's wrong!
Since next(action);
was the first thing I've performed in the middleware, I couldn't return it this soon, so I've moved it at the end of the middleware. I've also decided to dispatch new actions instead of using next
for them (after all, they ARE new actions, it does make sense to send them to the whole chain of middlewares). My new middleware look like this now:
store => next => async (action) => {
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
try {
const currentUser = await api.getCurrentUser();
store.dispatch(actions.getCurrentUserSuccess(currentUser));
} catch (error) {
store.dispatch(actions.getCurrentUserFailure(error));
}
break;
}
default:
break;
}
return next(action);
};
It looks great, but I have now another issue: since store.dispatch
is synchronous, next(action)
is called after. That means that my reducers receive the REQUEST
action AFTER the SUCCESS
or FAILURE
ones :(
I think one solution could be using good old promises instead of await
:
store => next => async (action) => {
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
api.getCurrentUser()
.then((currentUser) => {
store.dispatch(actions.getCurrentUserSuccess(currentUser));
})
.catch((error) => {
store.dispatch(actions.getCurrentUserFailure(error));
});
break;
}
default:
break;
}
return next(action);
};
Another idea would be to wrap the store.dispatch
with a setTimeout
:
store => next => async (action) => {
switch (action.type) {
case'GET_CURRENT_USER_REQUEST': {
try {
const currentUser = await api.getCurrentUser();
setTimeout(() => store.dispatch(actions.getCurrentUserSuccess(currentUser)));
} catch (error) {
setTimeout(() => store.dispatch(actions.getCurrentUserFailure(error)));
}
break;
}
default:
break;
}
return next(action);
};
To be honest, I don't really like these two solutions, they feel so hacky...
So here is my question: how am I suppose to handle my problem? Is there a more clean way to do that?
Thanks in advance :)
Share Improve this question edited Jul 5, 2018 at 8:14 KorHosik asked Jul 5, 2018 at 8:04 KorHosikKorHosik 1,2775 gold badges14 silver badges25 bronze badges2 Answers
Reset to default 4I think your first solution is near to the clean answer. You can introduce the term sideEffects
which are async functions executed whenever your action is dispatch. Your middleware function is still synchronize and the action will be dispatch right away. Here is an example:
getCurrentUserSideEffects = async (action) => {
api.getCurrentUser()
.then((currentUser) => {
store.dispatch(actions.getCurrentUserSuccess(currentUser));
})
.catch((error) => {
store.dispatch(actions.getCurrentUserFailure(error));
});
}
<other side effects>
store => next => action => {
switch (action.type) {
case 'GET_CURRENT_USER_REQUEST': {
getCurrentUserSideEffects(action);
break;
}
default:
break;
}
return next(action);
};
I think this idea is pretty similar to redux saga, but simpler and easier to understand.
Seems like what you are trying to do is similar to Redux-Saga I suggest you take a look at their library.
Taken from their example
// worker Saga: will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
/*
Starts fetchUser on each dispatched `USER_FETCH_REQUESTED` action.
Allows concurrent fetches of user.
*/
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
本文标签: javascriptHow to dispatch actions from Redux middleware in correct orderStack Overflow
版权声明:本文标题:javascript - How to dispatch actions from Redux middleware in correct order - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741511947a2382657.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论