rechyons

no more verbose actions and reducers

Usage no npm install needed!

<script type="module">
  import rechyons from 'https://cdn.skypack.dev/rechyons';
</script>

README

Rechyons

stability npm travis dm js-standard-style

Redux is no longer verbose

A library allows you use redux without writing actions and reducers, and still keep immutable

中文文档(Chinese Document)

Migrate

Because I lost my 2 factor authentic token, this project has moved to https://github.com/zhouhanseng/rechyons

Motivation

Redux has one disadvantage: it is painfully verbose. Each time we want to add a feature, we needed to type a lot of lines for

  • constants
  • action types,
  • action creators,
  • action handlers in the reducer
  • ...

Actually we bear this disadvantage for several years, and the situation seems to be getting worse. See verbose nightmare

It's time to rethink how to use redux. Why you need no Redux Saga

With rechyons, you no longer need the verbose lines above at all

Rechyons makes redux state easy to update and retrieve like normal js object,meanwhile keep its immutable.

// update
hyperstore.user.update({ name: "yourname" });

// retrieve
let username = hyperstore.user.name;

Usage

Example or a little bigger app

$ git clone git@github.com:ZhouHansen/rechyons.git
$ cd rechyons/example
$ yarn install
$ yarn start

Principle (8 minutes to read, easy than you imagine)

Install

Support both typescript and javascript

$ npm install rechyons

Antecedent

// your app tsconfig.json
{
  "compilerOptions": {
    "strict": false
  }
}

Setup redux store and rechyons

rechyons generate action and reducer for each of the state fields. Shape your initState's structure to this:

{
  moduleA: {keyA: somevalue, keyB: somevalue, keyC: somevalue},
  moduleB: {keyA: somevalue, keyB: somevalue, keyC: somevalue},
}

rechyons exports two functions rechyons.reducer() and rechyons().

rechyons.reducer() takes your init state to generate 'user/name', 'user/age', 'animal/category', 'animal/weight' four pair of action and reducer automatically. Then return the reducers to redux.combineReducers to create the store.

rechyons() swallows store.dispatch for calling the designated actions.

// store.ts
import { createStore, combineReducers } from "redux";

import rechyons from "rechyons";

let initState = {
  // moduleA
  user: {
    name: "zhc",
    age: 10,
  },
  // moduleB
  animal: {
    category: "dog",
    weight: 10,
  },
};

export let store = createStore(combineReducers(rechyons.reducer(initState)));

export default rechyons(initState, store.dispatch);

Get, bind and update state in component

rechyons(initState, store.dispatch) returns a hyperstore, hyperstore.user and hyperstore.animal both are instances of Rechyons class.

// TestComponent
import React from "react";
import { connect } from "react-redux";
import hyperstore from "./store";

export interface Props {
  name: string;
}

class TestComponent extends React.Component<Props, {}> {
  constructor(props: Props) {
    super(props);
  }

  render() {
    return (
      <div>
        <button
          data-testid="button"
          onClick={() => {
            hyperstore.user.update("name", "abc");
          }}
        >
          {this.props.name}
        </button>
      </div>
    );
  }
}

const MapStateToProps = (store) => {
  return {
    name: store[hyperstore.user.name],
  };
};

export default connect(MapStateToProps)(TestComponent);

Get data from state

store[hyperstore.user.name] equals to initState.user.name which is "zhc"; store[hyperstore.animal.weight] equals to initState.animal.weight which is 10; So we can use this to MapStateToProps()

Update state

Use hyperstore.user.update("name", "abc") or hyperstore.user.update({"name": "abc"})hyperstore.user.update("name", "abc") 对特指的 action 执行了store.dispatch({type: "user/name", "abc})

API

rechyons.reducer()

type ReducerType = { [key: string]: (state: any, action: AnyAction) => any };
type initStateType = { [key: string]: { [key: string]: any } };

rechyons.reducer: (initState: initStateType) => ReducerType

rechyons.reducer() return the reducers generated from initstate, so it only devotes to create redux store.

export let store = createStore(combineReducers(rechyons.reducer(initState)));

rechyons()

rechyons: (initState: initStateType, dispatch: Dispatch<AnyAction>) => { [key: string]: Rechyons }

Each hyperstore.someModule is a Rechyons instance

import hyperstore, { store } from "./store";
let hyperstore = rechyons(initState, store.dispatch);

// hyperstore.user is one of the Rechyons instances
console.log(hyperstore.user.name); // get the keyname output "user/name"
console.log(store[hyperstore.user.name]); // get the value output "zhc"

hyperstore.user.update({ name: "abc", age: 20 }); // change state
console.log(hyperstore.user.name); // output "abc"

Verbose nightmare

I want to add a like feature ❤ on the image people post in a social app like twitter.

export default {
  state: {
    //...
  },
  effects: {
    //... Thousands of lines
    *toggleLike({ payload }, { call, put }) {
      const { isLiked, id } = payload;
      if (isLiked) {
        yield call(services.setLike, id);
      } else {
        yield call(services.setUnLike, id);
      }
      yield put(toggleLikeSuccess({ id, isLiked }));
    },
  },
  reducer: {
    //...
  },
};

In models/somemodule.js, add a generator to commit actions in effects object.

export function toggleLikeSuccess({ id, isLiked }) {
  return {
    type: "toggleLikeSuccess",
    payload: {
      id,
      isLiked,
    },
  };
}

In actions/somemodule.js, define a new action.

export default {
  state: {
    //...
  },
  effects: {
    //... Thousands of lines
  },
  reducer: {
    //...Thousands of lines
    toggleLikeSuccess(state, { payload }) {
      const { id, isLiked } = payload;
      return {
        ...state,
        list: list.map((item) => {
          if (item.id === id) {
            const newLikeNum = isLiked ? item.like_num + 1 : item.like_num - 1;
            return {
              ...item,
              is_liked: isLiked,
              like_num: newLikeNum > 0 ? newLikeNum : 0,
            };
          }
          return item;
        }),
      };
    },
  },
};

In models/somemodule.js, add a reducer in reducer object.

const mapDispatchToProps = (dispatch) => ({
  dispatch,
  toggleLikeMyImgTxt: compose(
    dispatch,
    // ...
    actions.triggerAction("somemodule/toggleLike")
  ),
});

In components/somecomponent.js, map dispatch to props.

Life is too heavy

License

MIT