Git Product home page Git Product logo

multireducer's Introduction

multireducer

NPM Version NPM Downloads Build Status

multireducer is a utility to wrap many copies of a single Redux reducer into a single key-based reducer.

Installation

npm install --save multireducer

Why?

There are times when writing a Redux application where you might find yourself needing multiple copies of the same reducer. For example, you might need more than one list of the same type of object to be displayed. Rather than make a big reducer to handle list A, B, and C, and have action creators either in the form addToB(item) or addToList('B', item), it would be easier to write one "list" reducer, which is easier to write, reason about, and test, with a simpler add(item) API.

However, Redux won't let you do this:

import list from './reducers/list';

const reducer = combineReducers({
  a: list,		// WRONG
  b: list,		// WRONG
  c: list		// WRONG
});

Each of those reducers is going to respond the same to every action.

This is where multireducer comes in. Multireducer lets you mount the same reducer any number of times in your Redux state tree, as long as you pass the key that you mounted it on to your connected component.

Read the full API Documentation

Working Example

The react-redux-universal-hot-example project uses multireducer. See its reducer.js, which combines the plain vanilla counter.js duck, to a multireducer. The CounterButton.js connects to the multireducer, and the Home.js calls <CounterButton/> with a multireducerKey prop.

Example with multiple counters and ducks composition.

multireducer's People

Contributors

bertho-zero avatar erikras avatar greenkeeperio-bot avatar jsdmc avatar kovcic avatar soulko avatar yched avatar yesmeck avatar zincli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

multireducer's Issues

state is undefined

I have some reducer:

export default function someReducer (state = initState, action) {
  switch (action.type) {
    ....
  }
}

When i use it with multireducer there is error because action is undefined when multireducer initialize;
To fix it i write that:

export default function someReducer (state = initState, action) {
if (!action) return state;

....
}

or that:

export default function someReducer (state = initState, action = {}) {
  ...
}

redux-thunk compatibility

For regular redux reducer action I can return a function. Example:

export function fetchDataIfNeeded() {
  return (dispatch, getState) => {
    if (shouldFetchData(getState(), storeName)) {
      return dispatch(fetchData())
    } else {
      return Promise.resolve()
    }
  }
}

Is it possible also with multireducer? How?

I've tried code above but with no luck. Returned function was never executed.

Multireducer example is not working

Hello for everyone, i'm trying to introduce this example in my work, but it's not working. I need to have multiples multireducer key and i haven't succeeded. Can someone help me?

architecture comment

Thought you might want to consider this:
The solution I've settled on for this kind of thing in my own projects is to use the same reducer and action creator code, but with action types prefixed differently for each reducer mount point, as depicted in the code below. I would love if redux-form worked this way.

It took a long long time before I had the idea to do things this way but once I did I wish I had thought of it at the very beginning.

Advantages:

  • it's easier to read the action log since actions for different mount points have different types
  • more flexible with mounting reducers in arbitrary, deeper-nested areas of the state
  • you can move around where reducers are mounted without having to change state paths used to modify reducers or action creators

Disadvantages:

  • it may not be obvious from the action type what part of the state it affects, depending on what action type prefixes you use

I even created prefixReducer and prefixActionCreator functions in my mindfront-redux-utils package to decorate existing reducers and action creators that weren't designed to be created with an action type prefix option.

import {createReducer, combineReducers} from 'mindfront-redux-utils' // or redux-actions etc
import {List} from 'immutable'

const PUSH = 'PUSH'
const POP = 'POP'

function listActions(actionTypePrefix) {
  const pushType = actionTypePrefix + PUSH
  const popType = actionTypePrefix + POP
  return {
    push: (item) => ({type: pushType, payload: item}),
    pop: () => ({type: popType}),
  }
})

function listReducer(actionTypePrefix) {
  return createReducer(List(), {
    [actionTypePrefix + PUSH]: (state, action) => state.push(action.payload),
    [actionTypePrefix + POP]: (state) => state.pop(),
  })
}

const reducer = combineReducers({
  a: listReducer('a.'),
  b: listReducer('b.'),
  c: listReducer('c.'),
  d: combineReducers({
    e: listReducer('d.e.'),
  })
});

const aListActions = listActions('a.')
const bListActions = listActions('b.')
const deListActions = listActions('d.e.')

dispatch(aListActions.push(...))
dispatch(bListActions.pop())
dispatch(deListActions.push(...))
// etc

Example with prefixReducer and prefixActionCreator (which I only really like to do if I'm using 3rd-party reducers/action creators):

import {
  createReducer, combineReducers, prefixReducer, prefixActionCreator
} from 'mindfront-redux-utils'
import {mapValues} from 'lodash'
import {List} from 'immutable'

const PUSH = 'PUSH'
const POP = 'POP'

const push = (item) => ({type: pushType, payload: item})
const pop = () => ({type: popType})
const listActions = {push, pop}

const listReducer = createReducer(List(),    
  [PUSH]: (state, action) => state.push(action.payload),
  [POP]: (state) => state.pop(),
})

const reducer = combineReducers({
  a: prefixReducer('a.')(listReducer),
  b: prefixReducer('b.')(listReducer),
  c: prefixReducer('c.')(listReducer),
  d: combineReducers({
    e: prefixReducer('d.e.')(listReducer),
  })
});

const aListActions = mapValues(listActions, prefixActionCreator('a.'))
const bListActions = mapValues(listActions, prefixActionCreator('b.'))
const deListActions = mapValues(listActions, prefixActionCreator('d.e.'))

dispatch(aListActions.push(...))
dispatch(bListActions.pop())
dispatch(deListActions.push(...))
// etc

add ability to use component props in connectMultireducer

Aloha!

It would be good to have the same api for * connectMultireducer* as react-redux connect has.
https://github.com/rackt/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options

In my usecase I want to use ownProps of component to connect to some kind of key-value structure in state where key - is a property of component connected using multireducer.

For such usecase fix is pretty simple. But to support the same api as original react-redux connect - it might take a little bit more time.

What's your opinion about that feature?

Cheers.

doc decorators clarification

Hey @erikras, I'm studying the components in react-redux-universal-hot-example like CounterButton.js and I came across the es7 decorator syntax for the first time. I looked into it, found a great article here.

I saw that you weren't using that syntax in the readme for this lib. In STEP 2 you show a different syntax, by passing the component in to the multireducer. I was wondering if you could detail the @ decorator syntax as well, or were open to a pull, I could take a stab at it. Thoughts?

Toggling object returns null

I am using the same reducer logic in the read me. The idea is to toggle a class depending on which button you clicked on. Each event fires, but my object is not toggling. Any thoughts?

App:


import React from "react"
import { bindActionCreators } from 'multireducer';
import { connect } from "react-redux"
import Button from "./Button"
import * as toggleactionCreators from '../actions/toggleActions';

//const mapStateToProps = state => ({ hidden: state.toggleCollection });
const mapStateToProps = (state, { as }) => ({ hidden: state.toggleCollection[as] });
const mapDispatchToProps = (dispatch, { as }) => bindActionCreators({...toggleactionCreators}, dispatch, as)

class Main extends React.Component {

  toggleDiv() {
    this.props.toggleDiv();
    console.log(this.props)
  }

  render() {
    const { hidden } = this.props;
    return (
      <div>
      <Button as="toggleA" onClick={this.toggleDiv.bind(this)} />
      <Button as="toggleB" onClick={this.toggleDiv.bind(this)} />
      <div>
        <h3 as="toggleA" className={ hidden ? null : "toggled"} >Good Day!</h3>
        <h1 as="toggleB" className={ hidden ? null : "toggled"} >Hello There!</h1>
      </div>
    </div>
    )    
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Main);

toggleAction:

export const toggleDiv = () => {
  return {
    type: 'TOGGLE_DIV',
  }
}

toggleReducer:

const toggle = (state = { hidden: false}, action) => {
  switch(action.type) {
    case 'TOGGLE_DIV':
      return Object.assign({}, ...state, {hidden: !state.hidden});
    default:
      return state;
  }
};
export default toggle;

Index Reducer:

import multireducer from 'multireducer';
import { combineReducers } from "redux"
import toggle from "./toggleReducer"

const thereducer = combineReducers({
  toggleCollection: multireducer({
    toggleA : toggle,
    toggleB : toggle
  })
});

export default thereducer;

Mounting a reducer on the fly

I have a use-case where users can add Widgets to a dashboard on runtime. Any pointers on how to mount a reducer on the fly with multireducer would be helpful.

After including the multireducer the actions dissaper

Greetings!

First of all the multireducer lib is exactly what I'am looking for to reuse some of my containers. But after I included the multireducer and adapted our code to your examples, the actions became undefined.
I use React-redux 4.4.2, Redux 3.5.2 and React 15.
The problem appears during the LOCATION_CHANGE.action while loding the page.
It seems that the system crash inside the combineReducer where the multireducer is included.
Here is an example of my code:

Reducers:

const reducer = combineReducers({
  listCollection: multireducer({
    list1: list,
    list2: list,
  })
});

action:

export function listInit(initData) {
  return {
    type: types.LIST_INIT,
    initData,
  };
}

reducer:

export default function list(state = initialState, action) {
// Here is the problem: action is undefined
  switch (action.type) {
    case types.LIST_INIT:
      return {
        data: action.initData,
        orgData: action.initData,
        ...state,
      }; .....

Thanks for your help!!

Multireducer, Redux Saga, wrapAction

I'm wondering if there's a canonical way of put()ing actions using Redux Saga. Simply calling put() with the created action won't work in conjunction with multireducer since we're not supplying a reducerKey.

Looking at the previous releases, it seems like it was just recently that wrapAction was removed from the public api. Was there a reason for removing this? If not, I suggest exporting it once again, since it seems to allow a cleaner integration with Redux Saga than calling a wrapped dispatch.

What's your opinion?

Only works using 'multireducer' as key?

I am trying to use this for a project, and notice that it doesn't seem to work unless I use the key 'multireducer' in my top level 'combineReducers' reducer.

So this works:

export default combineReducers({
  multireducer: multireducer({
    list1: list,
    list2: list
  }),
});

But this doesn't work:

export default combineReducers({
  myLists: multireducer({
    list1: list,
    list2: list
  }),
});

It seems like 'multireducer' is hardcoded in connectMultireducer.js line 32:

const slice = state.multireducer[multireducerKey];

Let me know if I'm misunderstanding something. But the above behavior seems to violate the way you say to use multireducer in the readme, and seems to limit you to only using one multireducer per project. Not sure the best way to solve this, maybe just change the behavior of multireducerConnect to be the same as react's connect, passing in the full state to the first argument rather than a slice of state?

Use with redux-router

Is it possible to provide multireducerKey when my container is used as route component in redux-router?

Example (it doesn't work of course):

<ReduxRouter>
  <Route path="/orders" component={OrdersList} multireducerKey='orders'/>
  <Route path="/users" component={UsersList} multireducerKey='users'/>
  ...  

Anything like that is possible or I need to create wrapper page component for my multireducer container?

load data with multireducer actions for deferredFetch on server

I have a multireducer and i want to populate it on the server, not quite sure how to get the actions from the multireduced store.

export default combineReducers({
  router: routerStateReducer,
  multireducer: multireducer({
    a: store,
    b: store,
    c: store
  }),
})
import {load} from 'redux/modules/store'

function fetchDataDeferred(getState, dispatch) {
  //how to load data for multireducer stores
}

@connectData(null, fetchDataDeferred)
class blah extends Component { ... }

Initial action with no type breaks with redux-actions's handleActions()

The reducer generated by plainMultireducer starts by applying an empty action ({}) to the decorated reducers, to let them generate the initial values.

This empty action, having no 'type', is not FSA-compliant, and happens to break with reducers created with redux-actions's handleActions() :

import { createStore } from 'redux'
import { handleActions } from 'redux-actions';
import multireducer from 'multireducer';
const list = handleActions({
  INCREMENT: (state, action) => ({
    counter: state.counter + action.payload
  }),
  DECREMENT: (state, action) => ({
    counter: state.counter - action.payload
  })
}, { counter: 0 });
const reducer = multireducer({
  a: list,
  b: list
});
const store = createStore(reducer);

breaks with "Cannot read property 'toSring' of undefined" when the reducer generated by handleActions() tries to run action.type.toString() when action === {}.

That .toString() call was added in the latest v0.12.0 of react-actions, to support a advanced/edge use case - see redux-utilities/redux-actions#113.
Arguably it's a bit cavalier for an FSA library to internally use "action.type as an object with a toString() method" :-)

Still, using an empty action with no type to bootstrap the reducer seems somewhat wrong ? Why not do like redux does with its '@@redux/INIT' action type ?

Assist with error : Uncaught TypeError: (0 , _multireducer.multireducer) is not a function

I am getting this error and I'm not sure why? Any ideas?
Uncaught TypeError: (0 , _multireducer.multireducer) is not a function

Code :

import { combineReducers } from 'redux';
import { multireducer } from 'multireducer';
import IssueEvents from './IssueEvents';
const rootReducer = combineReducers({
    issueEventCollections : multireducer({
        issued : IssueEvents,
        created : IssueEvents
    })
});

WIth immutable

If I have a full immutable state, this is not working.

dynamically create new copies

Is there a way to dynamically add/create copies?

Using your universal example, if the user took an action to create a new counter-- how would a new copy of the counter reducer be created?

babelHelpers.typeof is not a function

Getting this error when I install this in my react native project. The relevant node modules are there but Im still getting this error. My .babelrc has only presets: ["react-native"] and adding es2015 and stage-0 doesn't help

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.