1.0.1 • Published 5 years ago

react-redux-mill v1.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
5 years ago

react-redux-mill

  1. What is it?
  2. Reasons to use it
  3. Installation
  4. Using
  5. Without and with comparison

What is it?

react-redux-mill is the easy way to use redux by one object. Main idea is possibility to use only one object to use actions, action creators and store.

You should try it if

  • You hate a lot of actions constants and very long imports of these constants
  • You hate a lot of equal action creators and very long imports
  • You want to use toor reducer path once in one place

Installation

npm install --save react-redux-mill

Using

It is easy to understand but there are some details.

Create your reducer

  // myReducer.js
  import reduxMill from 'react-redux-mill';

  const initialState = { title: 'Default title' };

  const reducer = {
    EDIT_TITLE: (state, title) => ({ ...state, title }),
    SAVE: {
      // it is for SAVE action
      0: state => ({ state, saving: true, error: "" }),
      // it is for SAVE__END action
      END: (state, payload) => ({ ...state, saving: false }),
       // it is for SAVE__FAIL action
      FAIL: (state, payload) => ({ ...state, saving: false, error: payload.error })
    }
    // ...
  };

  export const store = reduxMill(
    initialState,
    reducer,
    'myStoreName',
    {
      // debug: true,
      // stateDebug: true,
      divider: "__",
      // it is divider between children and parent parts of key - SAVE__FAIL
      mainKey: 0
      // it is children key that means reaction for parent action
      // LOAD: { 0: state => ({ ...state }) }
      // is the same that
      // LOAD: state => ({ ...state })
    }
  );

  // selectors
  export const selectTitle = store(state => state.title);
  // ...

Connecting to your store

  // store.js
  import { createStore, applyMiddleware } from "redux";
  import { store: myReducer } from './myReducer';
  import { store: mySecondReducer } from './mySecondReducer';

  const reducers = {
    ...myReducer,
    // it will be 'myStoreName' key
    ...mySecondReducer
  };

  export default createStore(
    combineReducers(reducers),
    applyMiddleware(...)
  );

Using action creator in component

  import React, { PureComponent } from 'react';
  import { connect } from 'react-redux';
  import myReducer, { selectTitle } from './myReducer';

  class MyComp extends PureComponent {
    onClick = () => {
      const { changeTitle } = this.props;
      changeTitle('Title ' + Math.random());
    }

    render() {
      return (
        <div>
          <h3>{title}</h3>
        <button onClick={this.onClick}>
        </button>
        </div>
      )
    }
  }

  export default connect(
    (state) => ({
      title: selectTitle(state),
    }),
    {
      changeTitle: myReducer.EDIT_TITLE
      // myReducer.EDIT_TITLE is action creator function(payload)
    }
  )(MyComp);

(!) reducer object will be changed after call of reduxMill

It will be object with the same structure, but each value will be action creator function(payload).

  import reduxMill from 'react-redux-mill';
  const reducer = {
    EDIT_TITLE: (state, title) => ({ ...state, title }),
    SAVE: {
      0: state => state,
      END: state => state,
      FAIL: (state, payload) => ({ ...state, ...payload })
    }
  };
  export const store = reduxMill(
    initialState,
    reducer,
    'myStoreName',
    { divider: "__" }
  );

  // what will be in reducer object after reduxMill calling
  {
    EDIT_TITLE: function(payload) {
      this._ = 'EDIT_TITLE';
      this.toString = function() { return 'EDIT_TITLE' };
      this.toValue = function() { return 'EDIT_TITLE' };
      return { type: 'EDIT_TITLE': payload };
    },
    SAVE: function(payload) {
      this.END = function(payload) {
        this._ = 'SAVE__END';
        this.toString = function() { return 'SAVE__END' };
        this.toValue = function() { return 'SAVE__END' };
        return { type: 'SAVE__END': payload };
      },
      this.FAIL = function(payload) {
        this._ = 'SAVE__FAIL';
        this.toString = function() { return 'SAVE__FAIL' };
        this.toValue = function() { return 'SAVE__FAIL' };
        return { type: 'SAVE__FAIL': payload };
      },
      this._ = 'SAVE';
      this.toString = function() { return 'SAVE' };
      this.toValue = function() { return 'SAVE' };
      return { type: 'SAVE': payload };
    }
  };

  // hot it can be used
  console.log(reducer.EDIT_TITLE == 'EDIT_TITLE')
  // console:> true
  // (!!!) but
  console.log(reducer.EDIT_TITLE === 'EDIT_TITLE')
  // console:> false

  console.log(reducer.EDIT_TITLE._)
  // console:> EDIT_TITLE
  console.log(String(reducer.SAVE.FAIL))
  // console:> SAVE__FAIL
  console.log(reducer.EDIT_TITLE({ a: 10 }))
  // console:> { type: 'EDIT_TITLE', payload: { a: 10 } }

  console.log(reducer.SAVE.END({ a: 10 }))
  // console:> { type: 'SAVE__END', payload: { a: 10 } }

So reducer can be used as action type for redux-saga

  takeEvery(reducer.EDIT_TITLE, ...);

or as action creator

  reducer.EDIT_TITLE(newTitle);

Description

reduxMill(initialState, reducer, storeName, options): function(selector: function(store, ...args));

argstypedefaultdescription
initialStateObject-default state
reducerObject-Object or instance of your class
storeNameString-it is name for redux store
optionsObject{...}additional options

Options includes next props

argstypedefaultdescription
debugBooleanfalseenable debug console logging
stateDebugBooleanfalseenable debug state changes console logging
dividerString'_'it is divider between parent key and children subkey
mainKeyString0it is key for main handler for parent action name

It returns function that is factory of selectors for REDUXstoreName

  const store = reduxMill(initialState, reducer, 'storeName');
  const selectItem = store((state, id) => state.items[id]);
  // it return function(state[, ...args])
  const selectItem2 = (state, id) => state['storeName'].items[id];
  // selectItem the same that selectItem2
  // state['storeName'] - is our current store
  // ...

reducer is simple object

  import { select } from "js-split";
  import mill from "./mill";

  const storeName = "myItems";
  const initialState = {
    items: [],
    title: "",
    loading: false,
    adding: false,
    removing: false,
    error: false
  };

  const END = "END";
  const FAIL = "FAIL";

  const reducer = {
    EDIT_TITLE: (state, title) => ({ ...state, title }),
    GET_ITEMS: {
      0: state => ({ ...state, loading: true, error: "" }),
      [END]: (state, payload) =>
        ({ ...state, loading: false, items: payload }),
      [FAIL]: state =>
        ({
          ...state,
          loading: false,
          error: "Somthing is wrong with items =("
        })
    },
    ADD_ITEM: {
      0: state => ({ ...state, adding: true, error: "" }),
      [END]: (state, payload) => ({ ...state, adding: false, items: payload }),
      [FAIL]: state =>
        ({ ...state, adding: false, error: "The same item there is in list" })
    },
    REMOVE_ITEM: {
      0: state => ({ ...state, removing: true, error: "" }),
      [END]: (state, payload) =>
        ({ ...state, removing: false, items: payload }),
      [FAIL]: state =>
        ({ ...state, removing: false, error: "It can't be removed" })
    }
  };
  // Use the same handler for another action
  reducer.DELETE_ITEM = reducer.REMOVE_ITEM;

  export const store = mill(initialState, reducer, storeName, {
    divider: "__",
    mainKey: 0
  });

  export const selectItems = store(state => state.items);
  export const selectTitle = store(state => state.title);
  export const selectError = store(state => state.error);

  export default reducer;

reducer is instance of function

  import { select } from "js-split";
  import mill from "./mill";

  const storeName = "myItemsM";
  const initialState = {
    items: [],
    title: "",
    loading: false,
    adding: false,
    removing: false,
    error: false
  };

  const END = "END";
  const FAIL = "FAIL";

  const reducer = new function() {
    this.EDIT_TITLE = (state, title) => ({ ...state, title });
    this.GET_ITEMS = {
      0: state => ({ ...state, loading: true, error: "" }),
      [END]: (state, payload) =>
        ({ ...state, loading: false, items: payload }),
      [FAIL]: state =>
        ({
          ...state,
          loading: false,
          error: "Somthing is wrong with items =("
        })
    };

    this.ADD_ITEM_BASE = {
      0: state => ({ ...state, adding: true, error: "" }),
      [END]: (state, payload) => ({ ...state, adding: false, items: payload }),
      [FAIL]: state =>
        ({ ...state, adding: false, error: "The same item there is in list" })
    };
    this.ADD_ITEM = {
      0: state => this.ADD_ITEM_BASE[0],
      [END]: (state, payload) => ({ ...state, adding: false, items: payload }),
      [FAIL]: state =>
        ({
          ...state,
          adding: false,
          error: "The same item  is in the list"
        })
    };

    this.REMOVE_ITEM = {
      0: state => ({ ...state, removing: true, error: "" }),
      [END]: (state, payload) =>
        ({ ...state, removing: false, items: payload }),
      [FAIL]: state =>
        ({ ...state, removing: false, error: "It can't be removed" })
    };

    // Use the same handler for another action
    this.DELETE_ITEM = this.REMOVE_ITEM;
  }();

  export const store = mill(initialState, reducer, storeName, {
    divider: "__",
    mainKey: 0
  });
  export default reducer;

  export const selectItems = store(state => state.items);
  export const selectTitle = store(state => state.title);
  export const selectError = store(state => state.error);

reducer is instance of class

  import { select } from "js-split";
  import mill from "./mill";

  const storeName = "myItemsM";
  const initialState = {
    items: [],
    title: "",
    loading: false,
    adding: false,
    removing: false,
    error: false
  };

  const END = "END";
  const FAIL = "FAIL";

  const reducer = new class {
    EDIT_TITLE = (state, title) => ({ ...state, title });
    GET_ITEMS = {
      0: state => ({ ...state, loading: true, error: "" }),
      [END]: (state, payload) =>
        ({ ...state, loading: false, items: payload }),
      [FAIL]: state =>
        ({ ...state, {
          loading: false,
          error: "Somthing is wrong with items =("
        })
    };

    ADD_ITEM = {
      0: state => ({ ...state, adding: true, error: "" }),
      [END]: (state, payload) => ({ ...state, adding: false, items: payload }),
      [FAIL]: state =>
        ({ ...state, adding: false, error: "The same item there is in list" })
    };

    REMOVE_ITEM = {
      0: state => ({ ...state, removing: true, error: "" }),
      [END]: (state, payload) =>
        ({ ...state, removing: false, items: payload }),
      [FAIL]: state =>
        ({ ...state, removing: false, error: "It can't be removed" })
    };

    // Use the same handler for another action
    DELETE_ITEM = this.REMOVE_ITEM;
  }();

  export const store = mill(initialState, reducer, storeName, {
    divider: "__",
    mainKey: 0
  });
  export default reducer;

  export const selectItems = store(state => state.items);
  export const selectTitle = store(state => state.title);
  export const selectError = store(state => state.error);

Without and with comparison

Before => 64 lines / 4 actions constants / 7 action creators

Alt Text

After => 47 lines / 1 action creators export

Alt Text

External lib deep-mutation was used in examples, but it IS NOT required dependency.