Skip to content

gillchristian/safe-redux-ts

master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

safe-redux-ts 🌲

NOTE: this library is based on @martin_hotell's rex-tils library and his article Improved Redux type safety with TypeScript 2.8.

Instead of telling the program what types it should use, types are inferred from the implementation, so type checker gets out of our way!

Improved Redux type safety with TypeScript 2.8

Install

yarn add safe-redux-ts

npm i safe-redux-ts

Use

Define the actions:

// src/pages/MyPage/actions.ts

import { ActionsUnion, createAction } from 'safe-redux-ts';

export const INC = '[counter] increment';
export const DEC = '[counter] decrement';
export const INC_BY = '[counter] increment_by';
export const WITH_META = '[counter] with_meta';

export const Actions = {
  inc: () => createAction(INC),
  dec: () => createAction(DEC),
  incBY: (by: number) => createAction(INC_BY, by),
  withMeta: (by: number, meta: string) => createAction(WITH_META, by, meta),
};

export type Actions = ActionsUnion<typeof Actions>;

export type ActionTypes =
  | typeof INC
  | typeof DEC
  | typeof INC_BY
  | typeof WITH_META;

Handle the actions:

// src/pages/MyPage/reducer.ts

import { handleActions, Handler } from 'safe-redux-ts';

import { User } from '../types';

import { INC, DEC, INC_BY, WITH_META, Actions, ActionTypes } from './actions';

interface State {
  count: number;
}

const initialState: State = {
  count: 0,
};

// `Handler` type can be used when you don't want to define the handlers inline
const handleIncBy: Handler<State, typeof INC_BY, Actions> = (
  { count },
  { payload },
) => ({ count: count + payload });

const reducer = handleActions<State, ActionTypes, Actions>(
  {
    [INC]: ({ count }) => ({ count: count + 1 }),
    [DEC]: ({ count }) => ({ count: count - 1 }),
    [INC_BY]: handleIncBy,
    [WITH_META]: ({ count }, { payload }) => ({ count: count + payload }),
  },
  initialState,
);

export default reducer;

handleActions works with React's useReducer as well. In that case, the initialState state can be omitted.

// src/pages/MyPage/useMyReducer.ts

import { useReducer } from 'react';
import { handleActions } from 'safe-redux-ts';

import { User } from '../types';

import { INC, DEC, INC_BY, WITH_META, Actions, ActionTypes } from './actions';

interface State {
  count: number;
}

const reducer = handleActions<State, ActionTypes, Actions>({
  [INC]: ({ count }) => ({ count: count + 1 }),
  [DEC]: ({ count }) => ({ count: count - 1 }),
  [INC_BY]: ({ count }, { payload }) => ({ count: count + payload }),
  [WITH_META]: ({ count }, { payload }) => ({ count: count + payload }),
});

const useMyReducer = () => {
  const [state, dispatch] = useMyReducer(reducer, { count: 0 });

  return { state, dispatch };
};

export default useMyReducer;

Type utils

safe-redux-ts also provides some type utils to work with Redux.

BindAction

Changes the return type of an action creator to void. In the context of a component the only important part of an action is the types of it's arguments. We don't rely on the return type.

// src/pages/MyPage/actions.ts

import { ActionsUnion, createAction } from 'safe-redux-ts';

export const INC = '[counter] increment';
export const DEC = '[counter] decrement';
export const INC_BY = '[counter] increment_BY';

export const Actions = {
  incBy: (by: number) => createAction(INC_BY, { by }),
};

export type Actions = ActionsUnion<typeof Actions>;

// src/pages/MyPage/MyPage.container.ts

import { connect } from 'react-redux';
import { BindAction } from 'safe-redux-ts';

import { Actions } from './actions';
import MyPage from './MyPage';

interface StateProps {
  count: number;
}

interface DispatchProps {
  incBy: BindAction<typeof Actions.incBy>; // (arg: number) => void
}

type MyPageProps = StateProps & DispatchProps;

export default connect<StateProps, DispatchProps>((s) => ({ count: s.count }), {
  incBy: Actions.incBy,
})(MyPage);

Differences with rex-tils

  • Added handleActions to create type safe reducers.
  • Smaller API. safe-redux-ts only exports a few functions and types:
    • Functions: createAction and handleActions.
    • Types: Action, ActionsUnion, ActionsOfType, Handler and BindAction.

LICENSE

MIT License © Christian Gill

Forked from housinganywhere/safe-redux.