Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update docs to warn about subscribing directly to state$ #766

Open
robmcm opened this issue Mar 16, 2022 · 2 comments
Open

Update docs to warn about subscribing directly to state$ #766

robmcm opened this issue Mar 16, 2022 · 2 comments

Comments

@robmcm
Copy link

robmcm commented Mar 16, 2022

Feature request for docs

What is the current behavior?
Subscribing to the state$ can be problematic if you later rely on the current value of the action$.
It is intended that the state$ emits first, as most examples suggest starting with a subscription to the action$, however if someone chooses to subscribe to the state$ they should be away that pulling in the current action$ value with combineLatest() will be incorrect (it will be the previous value).

state$.pipe(
    map(state => state.accountBalance),
    distinctUntilChanged(),
    withLatestFrom(action$),
    tap(([balance, action]) => {
        console.log('The reduces updated the balance in response to ', action.type); // This is actually the previous value...
    })
)

What is the expected behavior?
This is expected (I can't think of a logical alternative), however it's not obvious and can be really confusing.

Suggested work around in the docs:

state$.pipe(
    sample(action$),
    map(state => state.accountBalance),
    distinctUntilChanged(),
    withLatestFrom(action$),
    tap(([balance, action]) => {
        console.log('The reduces updated the balance in response to ', action.type); // The action is correct
    })
)

The above is slightly nicer than:

action$.pipe(
    withLatestFrom(state$),
    pick('1')
);

or you could (but a less reactive approach)

action$.pipe(
    mapTo(state$.value)
);

Which versions of redux-observable, and which browser and OS are affected by this issue? Did this work in previous versions of redux-observable?
All

robmcm added a commit to robmcm/redux-observable that referenced this issue Mar 16, 2022
More info here: redux-observable#766

Not sure if it's worth adding workarounds as it's quite an edge case. Could potentially say it's isn't advised the the action is also needed?
@jayphelps
Copy link
Member

Interesting. I haven't come across anyone with this pattern of withLatestFrom(action$) instead of withLatestFrom(state$). I would generally discourage the former--mentally I think if you need to respond to an action it would be more idiomatic conceptually to listen for it as the primary, then state$ as secondary or use state$.value. The only real reason state is offered as an observable is because I found it was non-obvious to people that they could effectively make the same thing but just doing action$.map(() => store.getState()).

That said, I'm keeping an open mind! If you're still interested in this topic, would you mind elaborating on some patterns you think are better using withLatestFrom(action$)?

Side note: is pick('1') a custom operator?

@robmcm
Copy link
Author

robmcm commented Apr 20, 2022

The use case is probably one of the more complex with multiple slices which are combined and re-used in multiple sub applications. The main reason for favoring state is because it's the source of truth, we have found relying on a specific action can become fragile because of the following:

  • Slice is changed by an unexpected action
    • A new action is added and the epic not updated.
    • A slice is affected by another reducer (in a reduce-reducer context which is sadly sometimes unavoidable).
  • The action didn't actually change state
    • By consolidating business logic in the reducer and treating it as a state machine.
    • We have been burnt in the past by components accidentally dispatching events (pre-redux) that should only have been triggered once. This can be hard to detect and trust especially with third-party components.

These cases aren't applicable to every application or even every slice, but to avoid accidents our rule of thumb is to avoid using the action$ directly. I imagine this is unlikely to be an issue for most usages, so I think it's fine to advise against it, but it would be nice to be explicit about the ordering in the docs.

Happy to discuss further if you are interested.

(Sorry I meant pluck not pick :) good spot )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants