admin管理员组文章数量:1391947
I have the following scenario in redux-observable. I have a ponent which detects which backend to use and should set the backend URL used by the api-client. Both the client and URL are held in the global state object.
The order of execution should be: 1. check backend 2. on error replace backend URL held in state 3. trigger 3 actions to load resources using new backend state URL
What i did so far is, in step 1. access the state$ object from within my epic and modify the backed URL. This seems to only half work. The state is updated by actions triggered in 3. still see the old state and use the wrong backend.
What is the standard way to update state in between actions if you depend on the order of execution?
My API-Epic looks like this:
export const authenticate = (action$, state$) => action$.pipe(
ofType(actions.API_AUTHENTICATE),
mergeMap(action =>
from(state$.value.apiState.apiClient.authenticate(state$.value.apiState.bearer)).pipe(
map(bearer => apiActions.authenticatedSuccess(bearer))
)
)
)
export const authenticatedSuccess = (action$, state$) => action$.pipe(
ofType(actions.API_AUTHENTICATED_SUCCESS),
concatMap(action => concat(
of(resourceActions.doLoadAResource()),
of(resourceActions.doLoadOtherResource()),
of(resourceActions.doLoadSomethingElse()))
)
)
I have the following scenario in redux-observable. I have a ponent which detects which backend to use and should set the backend URL used by the api-client. Both the client and URL are held in the global state object.
The order of execution should be: 1. check backend 2. on error replace backend URL held in state 3. trigger 3 actions to load resources using new backend state URL
What i did so far is, in step 1. access the state$ object from within my epic and modify the backed URL. This seems to only half work. The state is updated by actions triggered in 3. still see the old state and use the wrong backend.
What is the standard way to update state in between actions if you depend on the order of execution?
My API-Epic looks like this:
export const authenticate = (action$, state$) => action$.pipe(
ofType(actions.API_AUTHENTICATE),
mergeMap(action =>
from(state$.value.apiState.apiClient.authenticate(state$.value.apiState.bearer)).pipe(
map(bearer => apiActions.authenticatedSuccess(bearer))
)
)
)
export const authenticatedSuccess = (action$, state$) => action$.pipe(
ofType(actions.API_AUTHENTICATED_SUCCESS),
concatMap(action => concat(
of(resourceActions.doLoadAResource()),
of(resourceActions.doLoadOtherResource()),
of(resourceActions.doLoadSomethingElse()))
)
)
Share
Improve this question
asked Apr 8, 2019 at 8:30
light_303light_303
2,1113 gold badges19 silver badges36 bronze badges
1
-
First, what does the
authenticate
function on yourapiClient
do and what does it return? Second, it's not clear when the backend URL is updated. Does that happen in a reducer in response to yourapiActions.authenticatedSuccess
action? Finally, what do theresourceActions.*
actions do? Do they kick off other epics? – seniorquico Commented Apr 9, 2019 at 20:40
1 Answer
Reset to default 8A mon approach I've found users discussing on GitHub & StackOverflow is chaining multiple epics, much like what I believe your example tries to demonstrate. The first epic dispatches an action when it's "done". A reducer listens for this action and updates the store's state. A second epic (or many additional epics if you want concurrent operations) listen for this same action and kick off the next sequence of the workflow. The secondary epics run after the reducers and thus see the updated state. From the docs:
Epics run alongside the normal Redux dispatch channel, after the reducers have already received them...
I have found the chaining approach works well to decouple phases of a larger workflow. You may want the decoupling for design reasons (such as separation of concerns), to reuse smaller portions of the larger workflow, or to make smaller units for easier testing. It's an easy approach to implement when your epic is dispatching actions in between the different phases of the larger workflow.
However, keep in mind that state$
is an observable. You can use it to get the current value at any point in time -- including between dispatching different actions inside a single epic. For example, consider the following and assume our store keeps a simple counter:
export const workflow = (action$, state$) => action$.pipe(
ofType(constants.START),
withLatestFrom(state$),
mergeMap(([action, state]) => // "state" is the value when the START action was dispatched
concat(
of(actions.increment()),
state$.pipe(
first(),
map(state => // this new "state" is the _incremented_ value!
actions.decrement()),
),
defer(() => {
const state = state$.value // this new "state" is now the _decremented_ value!
return empty()
}),
),
),
)
There are lots of ways to get the current state from the observable!
Regarding the following line of code in your example:
state$.value.apiState.apiClient.authenticate(state$.value.apiState.bearer)
First, passing an API client around using the state is not a mon/remended pattern. You may want to look at injecting the API client as a dependency to your epics (this makes unit testing much easier!). Second, it's not clear how the API client is getting the current backend URL from the state. Is it possible the API client is using a cached version of the state? If yes, you may want to refactor your authenticate
method and pass in the current backend URL.
Here's an example that handles errors and incorporates the above:
/**
* Let's assume the state looks like the following:
* state: {
* apiState: {
* backend: "URL",
* bearer: "token"
* }
*/
// Note how the API client is injected as a dependency
export const authenticate = (action$, state$, { apiClient }) => action$.pipe(
ofType(actions.API_AUTHENTICATE),
withLatestFrom(state$),
mergeMap(([action, state]) =>
// Try to authenticate against the current backend URL
from(apiClient.authenticate(state.apiState.backend, state.apiState.bearer)).pipe(
// On success, dispatch an action to kick off the chained epic(s)
map(bearer => apiActions.authenticatedSuccess(bearer)),
// On failure, dispatch two actions:
// 1) an action that replaces the backend URL in the state
// 2) an action that restarts _this_ epic using the new/replaced backend URL
catchError(error$ => of(apiActions.authenticatedFailed(), apiActions.authenticate()),
),
),
)
export const authenticatedSuccess = (action$, state$) => action$.pipe(
ofType(actions.API_AUTHENTICATED_SUCCESS),
...
)
Additionally, keep in mind when chaining epics that constructs like concat
will not wait for the chained epics to "finish". For example:
concat(
of(resourceActions.doLoadAResource()),
of(resourceActions.doLoadOtherResource()),
of(resourceActions.doLoadSomethingElse()))
)
If each of these doLoadXXX
actions "starts" an epic, all three will likely run concurrently. Each action will be dispatched one after another, and each epic will "start" running one after another without waiting for the previous one to "finish". This is because epics never really plete. They're long-lived, never ending streams. You will need to explicitly wait on some signal that identifies when doLoadAResource
pletes if you want to doLoadOtherResource
to run after doLoadAResource
.
本文标签: javascriptReduxObservable modify state and trigger follow up actionStack Overflow
版权声明:本文标题:javascript - Redux-Observable: modify state and trigger follow up action - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744689142a2619885.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论