-
-
Notifications
You must be signed in to change notification settings - Fork 99
Description
Is your feature request related to a real problem or use-case?
While using Epic
s from redux-observable
I find myself writing the same code over and over again. It would be great to have an abstraction of this while being properly typed.
Describe a solution including usage in code example
Let's say you have a regular async action:
export const fetchEmployees = createAsyncAction(
'@employees/FETCH_EMPLOYEES',
'@employees/FETCH_EMPLOYEES_SUCCESS',
'@employees/FETCH_EMPLOYEES_FAILURE',
'@employees/FETCH_EMPLOYEES_CANCEL'
)<undefined, Employee[], HttpError>();
With the corresponding Epic
:
const fetchEmployeesEpic: Epic<RootAction, RootAction, RootState, Services> = (action$, state$, { employeeService }) =>
action$.pipe(
filter(isActionOf(fetchEmployees.request)),
switchMap(({ payload }) =>
employeeService.getEmployees(payload).pipe(
map(fetchEmployees.success),
catchErrorAndHandleWithAction(fetchEmployees.failure),
takeUntilAction(action$, fetchEmployees.cancel)
)
)
);
The two pipeable operators catchErrorAndHandleWithAction
and takeUntilAction
are defined as:
export const takeUntilAction = <T>(
action$: Observable<RootAction>,
action: (payload: HttpError) => RootAction
): OperatorFunction<T, T> => takeUntil(action$.pipe(filter(isActionOf(action))));
export const catchErrorAndHandleWithAction = <T, R>(
action: (payload: HttpError) => R
): OperatorFunction<T, T | R> =>
catchError((response) => of(action(response)));
Now the following part inside the Epic
's switchMap
is pretty much the same in every epic
employeeService.getEmployees(payload).pipe(
map(fetchEmployees.success),
catchErrorAndHandleWithAction(fetchEmployees.failure),
takeUntilAction(action$, fetchEmployees.cancel)
)
The goal is to abstract this in its own operator that might be used like
employeeService.getEmployees(payload).pipe(
mapUntilCatch(action$, fetchEmployees),
)
As a starting point I already tried
export const mapUntilCatch = <T>(action$: Observable<RootAction>, actions: any) =>
pipe(
map(actions.success),
catchErrorAndHandleWithAction(actions.failure),
takeUntilAction(action$, actions.cancel)
);
But I am running into problems with the generics and typing of actions
.
I already tried different variations like:
actions: { success: PayloadAC<TypeConstant, T[]>; cancel: never; failure: PayloadAC<TypeConstant, HttpError> }
actions: { success: PayloadAC<TypeConstant, T[]>; cancel: never; failure: PayloadAC<TypeConstant, HttpError> }
actions: ReturnType<AsyncActionBuilder<TypeConstant, TypeConstant, TypeConstant, TypeConstant>>
actions: ActionType<
AsyncActionCreator<[TypeConstant, any], [TypeConstant, T[]], [TypeConstant, HttpError], [TypeConstant, undefined]>
>
Who does this impact? Who is this for?
People using typescript and redux-observable
.
Describe alternatives you've considered (optional)
Using the pipe
operator right inside the Epic
works:
import { pipe } from 'rxjs';
employeeService.getEmployees(payload).pipe(
pipe(
map(fetchEmployees.success),
catchErrorAndHandleWithAction(fetchEmployees.failure),
takeUntilAction(action$, fetchEmployees.cancel)
)
)
The signature of that pipe
is the following:
(alias) pipe<Observable<Employee[]>, Observable<PayloadAction<"@employees/FETCH_EMPLOYEES_SUCCESS", Employee[]>>, Observable<PayloadAction<"@employees/FETCH_EMPLOYEES_SUCCESS", Employee[]> | PayloadAction<...>>, Observable<...>>(fn1: UnaryFunction<...>, fn2: UnaryFunction<...>, fn3: UnaryFunction<...>): UnaryFunction<...> (+10 overloads)
Additional context (optional)
In case you need the type of HttpError
:
export type HttpError = {
status?: number;
error?: string;
message?: string;
};
IssueHunt Summary
Sponsors (Total: $120.00)
issuehunt ($120.00)
Become a sponsor now!
Or submit a pull request to get the deposits!
Tips
- Checkout the Issuehunt explorer to discover more funded issues.
- Need some help from other developers? Add your repositories on IssueHunt to raise funds.