npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-mill

v1.3.0

Published

It is the easy way to use redux by one object

Downloads

27

Readme

redux-mill

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

What is it?

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 reducer path once in one place

Installation

npm install --save redux-mill

Using

It is easy to understand but there are some details.

Create your reducer

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

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

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

  export const store = reduxMill(
    initialState,
    reducer,
    'myStoreName',
    {
      // debug: true,
      // stateDebug: true,
      // nameAsPrefix: false,
      // if nameAsPrefix is true, then your action types will looks like -
      // `{storeName}{divider}{actionType}` => `myStoreName__EDIT_TITLE`
      // or `myStoreName__SAVE__FAIL` for children actions
      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 '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.type = 'EDIT_TITLE';
      this.toString = function() { return 'EDIT_TITLE' };
      this.valueOf = function() { return 'EDIT_TITLE' };
      return { type: 'EDIT_TITLE': payload };
    },
    SAVE: function(payload) {
      this.END = function(payload) {
        this._ = 'SAVE__END';
        this.type = 'SAVE__END';
        this.toString = function() { return 'SAVE__END' };
        this.valueOf = function() { return 'SAVE__END' };
        return { type: 'SAVE__END': payload };
      },
      this.FAIL = function(payload) {
        this._ = 'SAVE__FAIL';
        this.type = 'SAVE__FAIL';
        this.toString = function() { return 'SAVE__FAIL' };
        this.valueOf = function() { return 'SAVE__FAIL' };
        return { type: 'SAVE__FAIL': payload };
      },
      this._ = 'SAVE';
      this.type = 'SAVE';
      this.toString = function() { return 'SAVE' };
      this.valueOf = 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(reducer.EDIT_TITLE.type)
  // 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));

| args | type | default | description | | - | - | - | - | | initialState | Object | - | default state | | reducer | Object | - | Object or instance of your class | | storeName | String | - | it is name for redux store | | options | Object | {...} | additional options |

Options includes next props

| args | type | default | description | | - | - | - | - | | debug | Boolean | false | enable debug console logging | | stateDebug | Boolean | false | enable debug state changes console logging | | divider | String | '_' | it is divider between parent key and children sub-key | | nameAsPrefix | Boolean | false | enable adding storeName as prefix for action types - {storeName}{divider}{actionType} | | mainKey | String | 0 | it is key for main handler for parent action name | | reducerWrapper | Function(initState, Function) | | It is function wrapper for your reducer (e.g. immer) | | actionWrapper | Function(Function) | | It is function wrapper for each action | | actionCreatorWrapper | Function(Function, actionType) | | It is function wrapper for each action creator |

It returns function that is factory of selectors for REDUX[storeName]

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

Use with reducerWrapper

If you want to wrap your reducer, then you should use reducerWrapper. When we make reducer function we will wrap it into reducerWrapper(initialState, reducerFunction). reducerWrapper should return Function(state, action).

  import reduxMill from 'redux-mill';

  function wrapReducer(baseState, callback) {
    // callback - it is reducer function(state, action), returns state
    console.log('your reducer was wrapped', baseState, callback);
    return (state = baseState, action) => {
      console.log('reducer called with action: ', action);
      console.log('we will generate new state each time!');
      // don't repeat this trick with JSON.parse(JSON.stringify(...))
      // it will be applied for any result of reducer
      // and for current state if this reducer doesn't contain required action
      return JSON.parse(JSON.stringify(callback(state, action)));
    }
  }

  const initialState = {
    title: '',
    items: [
      { note: '11111' },
    ]
  }

  const reducer = {
    setTitle: (draft, payload) => { draft.title = payload }
    setNote: (draft, payload) => { draft.items[0].note = payload }
  }

  const store = reduxMill(initialState, reducer, 'storeName', { reducerWrapper: wrapReducer });

Test cases for reducerWrapper

Use with actionWrapper

If you want to wrap each action handler separately then you should use actionWrapper. Each action handler will be wrapped into actionWrapper(actionHandler). We expect that actionHandler is Function(state, action). actionWrapper should return Function(state, [payload, action]).

  import reduxMill from 'redux-mill';

  function wrapAction(callback) {
    // callback - it is action function(state, payload, action), returns state
    console.log('your action handler was wrapped', callback);
    return (state, payload, action) => {
      console.log('Action was called: ', action.type);
      console.log('we will generate new state');
      // I don't recommend to use this trick with JSON.parse(JSON.stringify(...))
      // But you can, because it will be called only for actions, not for default
      return JSON.parse(JSON.stringify(callback(state, payload, action)));
    }
  }

  const initialState = {
    title: '',
    items: [
      { note: '11111' },
    ]
  }

  const reducer = {
    setTitle: (draft, payload) => { draft.title = payload },
    setNote: (draft, payload) => { draft.items[0].note = payload }
  }

  const store = reduxMill(initialState, reducer, 'storeName', { actionWrapper: wrapAction });

  // if you wont to use actionWrapper, then you should wrap each handler by your self, it is the same
  // const reducer = {
  //   setTitle: produceAction((draft, { payload }) => { draft.title = payload }),
  //   setNote: produceAction((draft, { payload }) => { draft.items[0].note = payload })
  // }

Test cases for actionWrapper

Use with actionCreatorWrapper

If you want to wrap each action creator then you should use actionCreatorWrapper. Each action creator will be wrapped into actionCreatorWrapper(actionCreator, actionType). We expect that actionCreator is Function(payload). actionCreatorWrapper should return Function(...anyArgs).

  import reduxMill from 'redux-mill';

  function wrapActionCreator(baseActionCreator, actionType) {
    // we may return `baseActionCreator` based on any condition, e.g. `actionType` comparison
    if (actionType === 'setColor') return baseActionCreator;
    // baseActionCreator - it is action creator function(payload), returns { type: String, payload: Any }
    console.log('your action creator was wrapped', baseActionCreator);
    return (payload, addTime = false) => {
      const action = baseActionCreator(payload);
      if (addTime) {
        action.meta = { ...action.meta, currentTime: Date.now() };
      }
      console.log('Action creator was wrapped for: ', action.type);
      return action;
    }
  }

  const initialState = {
    title: '',
  }

  const reducer = {
    setTitle: (state, payload, action) => ({
      ...state,
      title: payload + ' ' + action.meta.currentTime
    }),
    setColor: (state, payload) => ({ ...state, color: payload })
  }

  const store = reduxMill(
    initialState,
    reducer,
    'storeName',
    { actionCreatorWrapper: wrapActionCreator }
  );

  // In this example `reducer` will look like:
  // {
  //   setTitle: wrapActionCreator((payload) => ({ type: 'setTitle', payload })),
  //   setColor: (payload) => ({ type: 'setColor', payload })
  // }

Test cases for actionCreatorWrapper

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: "Something 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: "Something 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: "Something 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.