Created
August 21, 2021 09:13
-
-
Save madyankin/3ce0df5a312daf1a0c549b6e2ec8d1c8 to your computer and use it in GitHub Desktop.
Type safe Redux helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable @typescript-eslint/generic-type-naming */ | |
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
import { ActionCreatorsMapObject, Middleware } from "redux"; | |
import { isPlainObject } from "lodash"; | |
// An interface to define action classes | |
export interface IAction { | |
readonly type: string; | |
} | |
type ActionCreatorResponse< | |
TReturn extends (...args: any[]) => any | |
> = ReturnType<ReturnType<TReturn>>; | |
type IsValidArg<T> = T extends object | |
? keyof T extends never | |
? false | |
: true | |
: true; | |
/* A helper type to infer the correct return type of an async action. | |
* | |
* An async action creator returns an async function which returns a promise or a plain Redux action. | |
* Without this helper async actions types will have the actions themselves as return types. | |
* Instead, we need to return a value returned from the async action. | |
* This helper removes the intemediate functions definitions and leaves the plain return types only. | |
*/ | |
type ReplaceReturnType<T, TNewReturn> = T extends ( | |
a: infer A, | |
b: infer B, | |
c: infer C, | |
d: infer D, | |
e: infer E, | |
f: infer F, | |
g: infer G, | |
h: infer H, | |
i: infer I, | |
j: infer J, | |
) => infer R | |
? (IsValidArg<J> extends true | |
? ( | |
a: A, | |
b: B, | |
c: C, | |
d: D, | |
e: E, | |
f: F, | |
g: G, | |
h: H, | |
i: I, | |
j: J, | |
) => TNewReturn | |
: IsValidArg<I> extends true | |
? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => TNewReturn | |
: IsValidArg<H> extends true | |
? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => TNewReturn | |
: IsValidArg<G> extends true | |
? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => TNewReturn | |
: IsValidArg<F> extends true | |
? (a: A, b: B, c: C, d: D, e: E, f: F) => TNewReturn | |
: IsValidArg<E> extends true | |
? (a: A, b: B, c: C, d: D, e: E) => TNewReturn | |
: IsValidArg<D> extends true | |
? (a: A, b: B, c: C, d: D) => TNewReturn | |
: IsValidArg<C> extends true | |
? (a: A, b: B, c: C) => TNewReturn | |
: IsValidArg<B> extends true | |
? (a: A, b: B) => TNewReturn | |
: IsValidArg<A> extends true | |
? (a: A) => TNewReturn | |
: () => TNewReturn) | |
: never; | |
/* A helper type to create an action creators object map type | |
* which we can export from the core to use it in the outer layers. | |
*/ | |
export type MakeBoundActionCreatorsMap<T extends ActionCreatorsMapObject> = { | |
[K in keyof T]: ReplaceReturnType<T[K], ActionCreatorResponse<T[K]>> | |
}; | |
/* A helper type to make a union type with all the actions. | |
* We use the union type in a reducer to infer actions types | |
*/ | |
export type ActionsUnion<A extends ActionCreatorsMapObject> = Extract< | |
ReturnType<A[keyof A]>, | |
IAction | |
>; | |
// Binds all of the methods of an object to the object | |
export function createActionsObject<T extends ActionCreatorsMapObject>( | |
actions: T, | |
): T { | |
Object.keys(actions).forEach(action => { | |
actions[action] = actions[action].bind(actions); | |
}); | |
return actions; | |
} | |
// Middleware to convert action classes' instances to plain objects | |
export const plainObjectMiddleware: Middleware< | |
object | |
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | |
> = () => next => action => { | |
if (typeof action !== "function" && !isPlainObject(action)) { | |
return next(Object.assign({}, action)); | |
} | |
return next(action); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment