/redux-chill

Library to reduce pain working with redux

Primary LanguageTypeScriptApache License 2.0Apache-2.0

Redux-Chill

Yet another library which made to reduce boilerplate working with redux.
It contains methods for action creators definitions, reducer definitions, redux-saga sagas.

Actions

Working with redux classically you will define constants to use them in action creators as type field and to match actions in reducers. Chill provides function to define your action creators and constants in one place. Also common case when your action can have few stages like SUCCESS, FAILURE, FINISH and so on.

import { make } from 'redux-chill';

// so action defined using redux-chill will look like:
// make requires only root action type ( you can pass any string, it's just format that i once saw in ngxs lib, and it's kinda better than constant case).
const getSomething = make('[namespace] action name')
  // defines function which will process passed arguments and returned value will be payload field
  .stage((id : number, otherArgument: SomeType) => ({ id, otherArgument }));
  // defines stage with name success, same as root action 2 argument defines how arguments will be processed to payload field
  .stage('success',  (something : Something)  => something)
  // Same example as above, but which another name and types
  .stage('failure', (error : Error) => error)
  // stage w/o arguments, so you can leave 2 argument empty
  .stage('finish');


// As result we get function with some additional fields
getSomething.type // [namespace] action name
getSomething(1, {}) // {type: string; payload: {  id: number; otherArgument: SomeType; }  }
getSomething.success({}) // {type: string; payload: Something; }
getSomething.success.type // [namespace] action name success

// another stages defined with name and function will work same as success above

getSomething.finish.type // [namespace] action name finish
getSomething.finish() // {type: string;}

Reducer

Chill also have reducer creator function which works good with synergy with generated by make actions. Under the hood it uses immer, so you don't need to think about immutability. Later probably there will be option to disable it if you want avoid losing performance ( check immer repo ).

import { reducer } from "redux-chill";

class State {
  public something: Something;
  public anotherField: AnotherType;
  public isFetching = false;
}

const someFeature = reducer(new State())
  // first arguments accepts: string | string[] | Func & {type: string;} | (Fun & {type: string})[]
  .on(getSomething, (state, payload) => {
    state; // will have type State because type provided above in reducer call ( you also can use generic param insteaed of passing default state )

    // payload fields from action. For different variations passed to first param of .on it will have different types;
    // string, string[] - unknown
    // Func & {type: string} - > ReturnType of passed function
    // (Func & {type: string})[] -> Union type of ReturnTypes
    payload;

    // not you can mutate it and immer will do the work, no need to return anything, just mutate
    state.isFetching = true;
  })
  // as described in case above it possible to pass different types together
  .on([getSomething, getSomething.success], () => {});

// NOTE: each time you use .on it returns new instance, so it will not mutate previous reducer, it can be used to reuse some handlers;

// NOTE2: If few .on cases matched, they will process state and action in order they was defined

Sagas

Redux-Saga is good library to manage async flows in state management part of your app. Working with redux-saga you probably encounter problem with necessity to name sagas with postfix like "saga" or prefix with watch to define watchers. But it can be resolving by grouping your sagas via class. For that Chill provides decorators to define how method of class will be runned by saga mw.

import { Saga } from 'redux-chill';
// i prefer to class class saga because it like postfix all method within with "saga".
class SomethingSaga {
  // Next will be few examples of @Saga decorator usage
  // Will run saga on boostrap
  @Saga()
  // Will wrap with watcher like: yield takeLatest(getSomething.type, ...hereWillBePassedMethodWhichDecoratorAttachedTo )
  @Saga(getSomething)
  // With 2 arguments, first will be takeLatest | takeEvery, but mostly you will probably use takeLatest for cancelation
  @Saga(takeLatest | takeEvery, getSomething)
  // Payload -> accepts function and just extract .payload field from ReturnType
  // context - will be described bellow
  public *getSomething({ id, otherArgument }: Payload<typeof getSomething>, context) {
    // saga code
  }
}


import { run } from 'redux-chill';


// saga mw instance as first arg
// second arg - class which contain methods decorated with @Saga
// thrid arg - context which will be passed to second arg when saga called
run(sagaMw, [ new SomethingSaga() ], { api, utils, ...other })