redux-typed-actions
Version:
An approach to type Redux actions and their payload and statically type checking them in Typescript
154 lines (124 loc) • 5.27 kB
Markdown
## Redux Typed Actions
An opinionated approach to type actions and their payload in Redux with statically type checking in Typescript.
This approach removes the most possible boilerplate of your actions & their creators in React and any other frameworks.
## Installation
Let's get started by installing it from [npm repository](https://www.npmjs.com/package/redux-typed-actions)
```sh
$ npm install --save-dev redux-typed-actions
```
or from [yarn repository](https://yarnpkg.com/en/package/redux-typed-actions)
```sh
$ yarn add --dev redux-typed-actions
```
## Usage
Let's do a quick example to see how this approach can improve type checking of redux actions
0. You have an ItemX interface:
**feature-x.types.ts**
```ts
export interface ItemX {
title: string;
}
```
1. First we define our actions:
**feature-x.actions.ts**
```ts
import { defineAction, defineScenarioAction, defineSymbolAction } from 'redux-typed-actions';
import { ItemX } from './feature-x.types';
// For this action we will have number as our payload
export const FeatureXAddTicketAction = defineAction<number>('[Feature X] Add Ticket');
/**
* This action is special, it's called a scenario-like action
* It notifies the system with status of a process covering from start to end.
* You can get Start/Success/Failure/Cancel from this action generator/creator
* There are 4 types that belong respectively to Start/Success/Failure/Cancel
* Note: The default type for payload of success and failure is
* string so you can skip them like `defineScenarioAction('MyActionName')`
*/
export const FeatureXLoadAction = defineScenarioAction<never, ItemX[], string>('[Feature X] Load');
// Let's have a symbol action just for fun
export const FeatureXDummySymbolAction = defineSymbolAction<ItemX[]>('[Feature X] Dummy Started');
```
Note: You can setup your own suffix for Start/Success/Failure/Cancel of scenario actions as following example:
```ts
import { factory } from 'redux-typed-actions';
// You must set them before defining actions
factory.setSuffixes({
start: '_REQUEST',
cancel: '_CANCEL',
success: '_SUCCESS',
failure: '_FAILURE',
});
```
2. Now we dispatch our actions:
**feature-x.component.ts**
```ts
import { ItemX } from './feature-x.types';
import { FeatureXAddTicketAction, FeatureXLoadAction } from '../feature-x.actions';
...
// React Redux solution to replace action creators:
// Let's define our component's state
interface FeatureXProps {
...
addTicket: typeof FeatureXAddTicketAction.strictGet; // StrictGet makes the payload mandatory
}
...
class FeatureX extends React.Component<FeatureXProps> {
...
addTicket = () => this.props.addTicket(1) // <- Static type checking
render() {
return (<button onClick={this.addTicket}>Add one ticket</button>); // />
}
}
// Let's hook the action to redux, and we're done
export default connect(undefined, { addTicket: FeatureXAddTicketAction.strictGet })(FeatureX);
// All Typescript frameworks:
// Dispatching a simple action
store.dispatch(FeatureXAddTicketAction.get(100));
// Let's start our scenario by dispatching our action
store.dispatch(FeatureXLoadAction.get());
// Now we dispatch the success action
const payload: ItemX[] = [{ title: 'item 1' }, { title: 'item 2' }];
dispatch(FeatureXLoadAction.success.get(payload));
// or simply a failure
dispatch(FeatureXLoadAction.failure.get('It failed because...'));
```
3. Now let's take a look at our reducers to see how we can type check the payloads:
**feature-x.reducers.ts**
```ts
import { PlainAction } from 'redux-typed-actions';
import { ItemX } from './feature-x.types';
import { FeatureXLoadAction } from '../feature-x.actions';
export interface ItemXState {
items: ItemX[];
loading: boolean;
}
export function reducer(state: ItemXState = InitialState, action: PlainAction): ItemXState {
if (FeatureXLoadAction.is(action)) {
// Within this branch our action variable has the right typings
return {
...state,
loading: true,
};
} else if (FeatureXLoadAction.success.is(action)) {
return {
...state,
loading: false,
items: action.payload, // <- Here we are checking types strongly
};
} else {
return state;
}
}
```
4. If you're fan of `redux-observables` then this part is for you:
**feature-x.epics.ts**
```ts
(action$, store) => action$
.ofType(FeatureXLoadAction.type)
.map(action => FeatureXLoadAction.cast(action)) // <- from here we will have all the typings right :)
.switchMap(action => Service()
.map(value => FeatureXLoadAction.success.get(repos))
.catch(() => Observable.of(FeatureXLoadAction.failure.get('Oops something went wrong!'))));
```
5. That's it
Take a look at [React](https://stackblitz.com/edit/react-redux-observable) or [Angular](https://stackblitz.com/edit/redux-typed-actions-example) examples for a closer look.