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
Type inference for ofType, removal of ActionsObservable in favor of just Observable #681
Conversation
|
I don't think it's necessary to deprecate ActionObservable before removing it. Changing the ofType method to an imported function is not a huge change and probably the only thing users will have to do, and they may have already done it. |
src/createEpicMiddleware.ts
Outdated
@@ -42,7 +41,7 @@ export function createEpicMiddleware<T extends Action, O extends T = T, S = void | |||
const stateSubject$ = new Subject<S>().pipe( | |||
observeOn(uniqueQueueScheduler) | |||
) as any as Subject<S>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: as any as Subject<S>
can be removed, now that a regular Observable
is used instead of ActionsObservable
.
I love this PR, as I think it simplifies both usage and the code quite a bit. Sorry if I just jumped in with this review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonderful! Thank you for pointing this out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out it couldn't just be removed as-is because while observeOn() uses RxJS's internal lift() function so it does return a Subject, the type definition does not reflect that with pipe() so from the type system point of view it's just an Observable and doesn't have .next() nor .asObservable(). No biggie as I was able to refactor the code in another way to avoid the casts.
…ble in favor of just Observable BREAKING CHANGE: ActionsObservable existed so we could provide an ofType() method to the prototype of action$, before RxJS had pipeable operators. Now that pipeable operators have been out for quite some time we are removing ActionsObservable in favor or using the pipeable ofType() instead. ```js // BEFORE function someEpic(action$) { return action$ .ofType('PING') .mapTo({ type: 'PONG' }); } // AFTER import { ofType } from 'redux-observable'; import { mapTo } from 'rxjs/operators'; function someEpic(action$) { return action$.pipe( ofType('PING') mapTo({ type: 'PONG' }) ); } ```
Thanks for all the reviews! I've cut a pre-release |
@jayphelps I noticed the release commit 9a85f64, but no publish seems to have been done to NPM (https://www.npmjs.com/package/redux-observable?activeTab=versions). Maybe there's some issue with the publishing pipeline. Should I create an issue for this? |
Whoa. I know 100% for sure the 2.0 alpha was released and tagged as next. It’s been installed and used in several apps that I’m aware of. Not sure what happened. I’ll look into it ASAP likely this weekend. Thanks for the heads up! Feel free to make a ticket if you’d like to subscribe to updates |
|
Phew! Thanks for checking that @evertbouw it must just be a UI bug or DB issue. I’m not able to try to install it right now, but anyone should let us know if you can’t install. |
That's weird... I didn't run Thanks a lot, sorry for the noise! edit: Running the npm command shows |
I'm trying to play around with this but can't seem to get the type inference to work. Where do I supply my actions union type for the inference to work? I have something like this: // Action is my union type of all possible action types for the app
export const loginEpic: Epic<Action, Action, RootState> = (action$) =>
action$.pipe(
ofType("LOGIN_REQUEST"),
mergeMap((action) =>
ajax({
url: `${API_URL}/auth/login`,
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: {
// I end up getting an error here, because the type gets incorrectly inferred
username: action.payload.username,
password: action.payload.password,
},
}).pipe(
map((response) => loginSuccess(response.response.token)),
catchError((error) => of(loginFailure(error.message))),
),
),
); |
@israelidanny Unfortunately this didn't actually make ofType infer the types correctly. I swear it worked when I PRd it, but apparently not. |
Coincidentally I just tried to update my code to use this and hitting same thing, thought I was doing something wrong. |
@jayphelps @kwonoj @israelidanny We are able to use type inference in our code for The below is a working example of inference for import { Epic, ofType } from 'redux-observable';
import { tap, ignoreElements } from 'rxjs/operators';
enum ActionType {
Update = 'COUNT_UPDATE',
Reset = 'COUNT_RESET'
}
type State = any;
type Action = ReturnType<typeof setCount> | ReturnType<typeof resetCount>;
const setCount = (count: number) => ({
type: ActionType.Update as const, // 1
payload: count
});
const resetCount = () => ({
type: ActionType.Reset as const // 1
});
export const testEpic: Epic<Action, Action, State, undefined> = action$ => action$.pipe(
ofType(ActionType.Update as const), // 1
// `payload` is correctly inferred to be `number`
tap(({ payload }) => console.log(payload + 1)),
ignoreElements()
); It might be possible to improve the typing so you don't need |
…ble in favor of just Observable (redux-observable#681) BREAKING CHANGE: ActionsObservable existed so we could provide an ofType() method to the prototype of action$, before RxJS had pipeable operators. Now that pipeable operators have been out for quite some time we are removing ActionsObservable in favor or using the pipeable ofType() instead. ```js // BEFORE function someEpic(action$) { return action$ .ofType('PING') .mapTo({ type: 'PONG' }); } // AFTER import { ofType } from 'redux-observable'; import { mapTo } from 'rxjs/operators'; function someEpic(action$) { return action$.pipe( ofType('PING') mapTo({ type: 'PONG' }) ); } ```
Heavily inspired by examples that @thorn0 @tjfryan did in #622
For v2.0.0 I'd like to finally have ofType infer the TypeScript types automatically, so that you don't need to pass in the generic param or define your filter action(s) as the argument type of the next operator. This requires some voodoo and a new-ish version of TypeScript, but I'm not yet sure which minimum version. I'll have to try them until it breaks, before merging this, then I can document it in the changelog/docs.
At the same time, I want to get away from ActionsObservable because in the pipeable operator world, there's no need for it and it just adds friction/learning.
The primary reason StateObservable is not BehaviorSubject is because we want to expose
state$.value
but _we do not want to exposestate$.next(something)
because that would be a huge footgun that wouldn't do what you might think--no reason to do it inside your epics.Have objections or other thoughts? Please lmk! We'll likely do the normal pre-alpha, alpha, beta, rc, etc cycle for 2.0, but depending on how smooth it goes, how many changes there are (don't expect too many) we might skip a step or two of those.