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-cube

v1.4.0

Published

App state manager. A set of wrappers which simplify the use of Redux and its mainstream ecosystem, reduce boilerplate, and support many patterns (like Sub App, Reducer Bundle, ...)

Downloads

81

Readme

redux-cube

< Back to Project WebCube

NPM Version

Nodei

Redux Cube is an app state manager. It is part of my effort to simplify the usage of all 'mainstream' tools and best practices mentioned in the Spellbook of Modern Web Dev. It can reduce boilerplate and support many patterns (like Sub App, Reducer Bundle, ...)

Slides: Introduction to Redux Cube

npm install --save redux-cube

New docs based on the new createCube API and webcube's SSR feature

Coming soon!

Examples


Old docs

Overview

Action Type

// sampleApp/hub.js
import { createHub } from 'redux-cube';

export default createHub();

createHub returns a Hub instance which is an namespace manager of action types and a set of helper functions used to generate standard action object (follow Flux Standard Action and other best practices) and action creators.

// sampleApp/actions/sample.js
import hub from '../hub';

export const { actions, types, typeDict } = hub.add('NAMESPACE/MORE_NAMESPACE/MY_TYPE');
export const { actions, types, typeDict } = hub.add('namespace.moreNamespace.myType');
export const { actions, types, typeDict } = hub.add({
  NAMESPACE: {
    MORE_NAMESPACE: {
      MY_TYPE: true
    },
  },
});
export const { actions, types, typeDict } = hub.add({
  namespace: {
    moreNamespace: {
      myType: true
    },
  },
});

The above codes are equivalent.

console.log(typeDict)
// {
//   'NAMESPACE/MORE_NAMESPACE/MY_TYPE': defaultActionCreator,
// }

console.log(types)
// {
//   namespace: {
//     moreNamespace: {
//       myType: 'NAMESPACE/MORE_NAMESPACE/MY_TYPE',
//     },
//   },
// }

console.log(actions)
// {
//   namespace: {
//     moreNamespace: {
//       myType: defaultActionCreator,
//     },
//   },
// }

The defaultActionCreator is equivalent to:

() => ({
  type: 'NAMESPACE/MORE_NAMESPACE/MY_TYPE',
  payload: defaultPayloadCreator,
  meta: undefined,
})

The defaultPayloadCreator is equivalent to:

a => a

Action Creators

export const { actions, types, typeDict } = hub.add('NAMESPACE/MORE_NAMESPACE/MY_TYPE', payloadCreator, metaCreator);
export const { actions, types, typeDict } = hub.add({
  namespace: {
    moreNamespace: {
      myType: true,
      myType2: payloadCreator,
      myType3: [payloadCreator, metaCreator],
      myType4: [
        (a, b) => ({ data: a + b }),
        (a, b) => ({ a, b }),
      ],
      myType5: {
        [hub.ACTION_CREATOR]: actionCreator,
      },
    },
  },
});
actions.namespace.moreNamespace.myType(10);
// or
typeDict['NAMESPACE/MORE_NAMESPACE/MY_TYPE'](10);
// results:
// {
//   "type": "NAMESPACE/MORE_NAMESPACE/MY_TYPE",
//   "payload": 10
// }
actions.namespace.moreNamespace.myType4(1, 10);
// or
typeDict['NAMESPACE/MORE_NAMESPACE/MY_TYPE_4'](1, 10);
// result:
// {
//   "type": "NAMESPACE/MORE_NAMESPACE/MY_TYPE_4",
//   "payload": { data: 11 },
//   "meta": { "a": 1, "b": 10 }
// }

Reducers

// sampleApp/reducers/sample.js
import hub from '../hub';

export const { reducer } = hub.handle({
  namespace: {
    moreNamespace: {
      myType: (state, { payload, meta }) => newState,
    },
  },
  anotherType: (state, { payload, meta }) => newState,
}, initialStateForASliceOfStore);

Async Action Creators / Side Effects

For common needs:

For API-related needs:

For complex needs:

// sampleApp/actions/users.js
import hub from '../hub';
// https://www.npmjs.com/package/hifetch
import hifetch from 'hifetch';
import { reset } from 'redux-form';

export const { actions, types, typeDict } = hub.add({
  users: {
    fetchAll: () =>
      // handle by redux-promise-middleware
      hifetch({
        url: '/v1/users/',
      }).send(),

    add: [
      (userId, userData, opt) =>
        // handled by Thunk Payload Middleware
        dispatch =>
          // handle by redux-promise-middleware
          hifetch({
            url: `/v1/users/${userId}`,
            method: 'put',
            data: userData,
            ...opt,
          }).send().then(response => {
            dispatch(reset('userInfos'));
            return response;
          }),
      userId => ({
        userId,
      }),
    ],

    delete: [
      (userId, userData) =>
        // handle by redux-promise-middleware
        hifetch({
          url: `/v1/users/${userId}`,
          method: 'delete',
        }).send(),
      userId => ({
        userId,
      }),
    ],

  },
});

// handle by redux-observable
export const epics = [
  action$ =>
    action$.pipe(
      ofType('USERS/DELETE_FULFILLED'),
      map(action => ({
        type: 'NOTIFY',
        payload: { text: 'DELETED!' },
      }))
    ),
];
// sampleApp/reducers/users.js
import hub from '../hub';
import Immutable from 'immutable';

export const { reducer, actions, types, typeDict } = hub.handle(
  {
    users: {
      fetchAllPending: state => state.set('isLoading', true),
      fetchAllFulfilled: (state, { payload }) =>
        state.mergeDeep({
          users: Immutable.fromJS(payload.data),
          isLoading: false,
        }),
      fetchAllRejected: state => state.set('isLoading', false),
      addPending: state => state.set('isLoading', true),
      // ...
      deleteFulfilled: (state, { payload }) =>
        state.set(
          'users',
          state.get('users').filter(user => user.get('id') !== payload.userId),
        ),
    },
  },
  Immutable.fromJS({
    users: [],
    isLoading: false,
  }),
);

How to use redux-cube with redux-source:

See webcube-examples

Ducks Modular / Reducer Bundle

Original Ducks Modular:

// widgets.js

// Action Types

// Action Creators

// Side Effects
// e.g. thunks, epics, etc

// Reducer

For reference:

Redux Cube's Reducer Bundle:

// sampleApp/ducks/actions/sample.js
import hub from '../../hub';

export const { actions, types, typeDict } = hub.add({
  myType1: asyncPayloadCreator1,
  myType2: asyncPayloadCreator2,
});
// sampleApp/ducks/sample.js
import hub from '../hub';
import { typeDict as existTypeDict } from './actions/sample';

export const { reducer, actions, types, typeDict } = hub.handle(
  // declared action type
  myType1: (state, { payload, meta }) => newState,
  // undeclared action type
  myType3: (state, { payload, meta }) => newState,
  // undeclared action type
  myType4: (state, { payload, meta }) => newState,
}, initialStateForASliceOfStore).mergeActions(existTypeDict);

export const epics = [
  action$ =>
    action$.pipe(/* ... */)
];
import { actions, types, typeDict } from '../reducers/sample';

console.log(actions);
// {
//   myType1: asyncActionCreator1,
//   myType2: asyncActionCreator2,
//   myType3: defaultActionCreator,
//   myType4: defaultActionCreator,
// }

console.log(typeDict);
// {
//   MY_TYPE_1: asyncActionCreator1,
//   MY_TYPE_2: asyncActionCreator2,
//   MY_TYPE_3: defaultActionCreator,
//   MY_TYPE_4: defaultActionCreator,
// }

It is highly recommended to use "duck" files as the only authoritative sources of action types and action creators.

Action files should be only used by "duck" files. They should be totally transparent to all other code.

Because hub.handle can automatically add actions for undeclared action types, you only need to manually call hub.add (and maybe write them in a separate action file) when these actions have side effects

Connect to React Components

// sampleApp/containers/Sample.jsx
import { connect } from 'redux-cube';
import { Bind } from 'lodash-decorators';
import { actions as todoActions } from '../ducks/todo';

@connect({
  selectors: [
    state => state.todo.input,
    state => state.todo.items,
  ],
  transform: (input, items) => ({
    input,
    items,
    count: items.filter(item => !item.isCompleted).length,
  }),
 actions: todoActions,
})
export default class Main extends PureComponent {
  @Bind
  handleInputChange(content) {
   this.props.actions.todo.changeInput(content);
  }
  render() {
    const { input, items, count } = this.props;

Te above code is equal to

// ...
import { createSelector } from 'reselect';

@connect({
  mapStateToProps: createSelector(
    [
      state => state.todo.input,
      state => state.todo.items,
    ],
    transform: (input, items) => ({
      input,
      items,
      count: items.filter(item => !item.isCompleted).length,
    }),
  ),
  mapDispatchToProps: dispatch => ({
    actions: {
      todo: {
        changeInput: (...args) => dispatch(
          todoActions.todo.changeInput(...args)
        ),
      },
    },
  }),
})
export default class Main extends PureComponent {

mapDispatchToProps option can be used together with actions option.

mapStateToProps option can be used together with selectors option.

Sub-Apps

// multipleTodoApp/todo/index.jsx
import React, { Component } from 'react';
import withPersist from 'redux-cube-with-persist';
import localforage from 'localforage';
import { createApp } from 'redux-cube';

import { reducer as sampleReducer, epics } from './ducks/sample';
import { reducer as sample2Reducer, epics } from './ducks/sample2';
import Sample from './containers/Sample';

@createApp(withPersist({
  reducers: {
    items: sampleReducer,
    sample2: {
      data: sample2Reducer,
    },
  },
  epics,
  preloadedState: typeof window !== 'undefined' && window._preloadTodoData,
  devToolsOptions: { name: 'TodoApp' },
  persistStorage: localforage,
  persistKey: 'todoRoot',
}))
class TodoApp extends Component {
  render() {
    return <Sample />;
  }
}

export const App = TodoApp;
// multipleTodoApp/index.jsx
import React, { Component } from 'react';
import { Route, Redirect, Switch } from 'react-router-dom';
import { createApp } from 'redux-cube';
import withRouter from 'redux-cube-with-router';
import { App as TodoApp } from './todo';

const JediTodoApp = () => (
  <TodoApp
    title="Jedi Todo"
    routePath="/jedi-todo"
    appConfig={{
      persistKey: 'jediTodoRoot',
      devToolsOptions: { name: 'JediTodoApp' },
      preloadedState:
        typeof window !== 'undefined' && window._preloadJediTodoData,
    }}
  />
);
const SithTodoApp = () => (
  <TodoApp
    title="Sith Todo"
    routePath="/sith-todo"
    appConfig={{
      persistKey: 'sithTodoRoot',
      devToolsOptions: { name: 'SithTodoApp' },
      preloadedState:
        typeof window !== 'undefined' && window._preloadSithTodoData,
    }}
  />
);

@createApp(withRouter({
  supportHtml5History: isDynamicUrl(),
  devToolsOptions: { name: 'EntryApp' },
}))
class EntryApp extends Component {
  render() {
    const TodoApps = () => (
      <div>
       <JediTodoApp />
       <SithTodoApp />
      </div>
    );
    const JumpToDefault = () => <Redirect to="jedi-todo/" />;
    return (
      <Switch>
        <Route path="/" exact={true} render={JumpToDefault} />
        <Route path="/" render={TodoApps} />
      </Switch>
    );
  }
}

export const App = EntryApp;

Immutable

Frozen plain object + immutability-helper / icepick / seamless-immutable / dot-prop-immutable / object-path-immutable / timm / updeep

@createApp(withPersist(withRouter({
  reducers,
  disableFreezeState: false, // default
  // ...
})))
import update from 'immutability-helper';
import hub from '../hub';

export const { reducer, actions, types, typeDict } = hub.handle({
  changeInput: (state, { payload: content }) =>
    update(state, {
     input: { $set: content },
    }),
  todo: {
    clearCompleted: state =>
      update(state, {
        items: {
         $apply: items =>
            items.map(item =>
              update(item, {
               isCompleted: { $set: false },
              }),
            ),
        },
      }),
  },
}, {
  items: [],
  input: '',
});

ImmutableJS object + redux-immutable

@createApp(withImmutable(withRouter({
  reducers, //
  // ...
})))

API

redux-cube

import { createApp, createHub, connect } from 'redux-cube'
createApp

It's mainly a wrapper of redux API and some must-have action middlewares, store enhancers, high-order reducers and high-order components.

It provides the support for Sub-App pattern (React component with its own isolated Redux store)

Options

  • reducers
  • reducer
  • epics
    • https://redux-observable.js.org/docs/basics/Epics.html
  • disableDevTools
  • devToolsOptions
    • https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md
  • disableFreezeState
  • loggerConfig
    • https://www.npmjs.com/package/redux-logger#options
  • promiseMiddlewareConfig`
    • https://github.com/pburtchaell/redux-promise-middleware/blob/4843291da348fc8ed633c41e6afbc796f7152cc6/src/index.js#L14
  • preloadedState
    • https://redux.js.org/docs/recipes/reducers/InitializingState.html
  • middlewares
    • https://redux.js.org/docs/advanced/Middleware.html
    • https://redux.js.org/docs/api/applyMiddleware.html
  • priorMiddlewares
  • enhancers
    • https://redux.js.org/docs/Glossary.html#store-enhancer
  • priorEnhancers
  • storeListeners
createHub

An superset and enhanced implement (almost a rewrite) of redux-actions.

It provides the support for namespace management and Reducer-Bundle-or-Ducks-Modular-like pattern

Options:

  • delimiter
connect

It's mainly a wrapper of react-redux and reselect

Options:

  • selectors
  • transform
  • mapStateToProps
  • actions
  • actionsProp
  • mapDispatchToProps

Plugins