2.2.1 • Published 7 years ago

redux-typed-modules v2.2.1

Weekly downloads
106
License
MPL-2.0
Repository
github
Last release
7 years ago

redux-typed-modules

Based on the ducks-modular-redux / Redux modules pattern, this library provides a simple, developer friendly, method for defining strongly typed modules in a pattern that will integrate seamlessly into projects currently following the Redux modules pattern.

Key features

  • exports interface matching existing Redux module pattern. No changes required outside the module file!
  • support for a transform method which is called after every reducer is run.
  • support for a method to be called to add extra 'module global' data to actions, useful for adding timing.
  • fully typed actions and reducers specific to the individual action!

Limitations

  • Actions accept up to seven arguments to allow for strong typing, if you need more, I recomment passing all arguments under a single object.

Installation

npm i --save redux-typed-modules

Usage

Here is a basic TODO module using redux-typed-modules.

src/modules/todos.ts

import { Module, generateID } from 'redux-typed-modules';

export interface TODO {
  id: string
  added: Date,
  task: string,
}

export interface TODOState {
  todos: Map<string, TODO>;
}

// Initialize the module with an initial state
//
// *Optional configuration
//  actionExtraData: () => T; -> define a method to be called after an action is run
//    whose return value will be merged with the action result before being passed
//    to a reducer. Useful for adding global fields to actions, like the time.
//
//  postReducer: (state: STATETYPE) => STATETYPE; -> define a method to be called
//    after every reducer is run. The result of an individual actions reducer is
//    passed in to this method for you to transform / and or perform any additional
//    tasks on all actions.
const module = new Module({
  initialState: {
    todos: Map<string, TODO>()
  },
});

// Define the action and reducer to handle adding a new TODO using the createAction method
//
// createAction returns a typed action function which can be dispatched.
//
// *Optional configuration
//  type: string; -> define a custom type name for the action. If no type value
//    is provided a unique string id is generated for each action. I'd recommend
//    defining a type name for better logging.
export const addTodo = module.createAction({
  // The returned method from createAction will call this method when dispatched
  // and will share the same types.
  action: (todo: string) => {
    return {
      // generateID is just a basic alphanumeric random string generator used within
      // the module class for automatic type name creation, it's exported if you want
      // to use it selsewhere for simple string generation
      id: generateID(7),
      task: todo,
    };
  },

  // Here state is the STATETYPE as defined by initialState
  // and action is typed as the return of the above action method
  // merged with {type: string} & the return of actionExtraData If
  // that method is defined on the constructor options
  //
  // The return of the reducer can be a partial state, this will be
  // merged with the existing state behind the scenes.
  //
  // This removes any bleedthrough of types from other actions to help
  // prevent any confusion or misuse of action properties typical when
  // being forced to use a module global action interface.
  reducer: (state, action) => {
    return {
      todos: state.todos.set(a.id, {
        id: a.id,
        task: a.task
      })
    };
  }
});

// Define the action and reducer to handle removing a TODO
export const removeTodo = module.createAction({
  action: (id: string) => {
    return {
      id: id
    };
  },

  reducer: (s, a) => {
    return {
      todos: s.todos.remove(a.id)
    };
  }
});

// Export the reducer, compiled from each of the reducers provided
// in the createAction methods
export default module.createReducer();

src/modules/reducer.ts - defined as you would normally

import {combineReducers} from 'redux';

import todos, {TODOState} from './todos';
// import other modules...

// Define the state as presented after combineReducers merges
// each state
export interface GlobalState {
  todos: TODOState,
  // add other state types
}

export default combineReducers({
  todos,
  // other module reducers
});

src/index.tsx - defined as you would normally

import * as React from 'react';
import * as ReactDom from 'react-dom';
import {Provider} from 'react-redux';

import reducer from './modules/reducer';
import Todos from './components/Todos';

const store = createStore(reducer);
ReactDom.render(
  <Provider store={store}>
    <Todos />
  </Provider>,
  document.getElementById('app')
);