Giter VIP home page Giter VIP logo

reduce-reducers's Introduction

reduce-reducers

Build Status npm Version npm Downloads Monthly

Reduce multiple reducers into a single reducer from left to right

Install

npm install reduce-reducers

Usage

import reduceReducers from 'reduce-reducers';

const initialState = { A: 0, B: 0 };

const addReducer = (state, payload) => ({ ...state, A: state.A + payload });
const multReducer = (state, payload) => ({ ...state, B: state.B * payload });

const reducer = reduceReducers(initialState, addReducer, multReducer);

const state = { A: 1, B: 2 };
const payload = 3;

reducer(state, payload); // { A: 4, B: 6 }

FAQ

Why?

Originally created to combine multiple Redux reducers that correspond to different actions (e.g. like this). Technically works with any reducer, not just with Redux, though I don't know of any other use cases.

What is the difference between reduceReducers and combineReducers?

This StackOverflow post explains it very well: https://stackoverflow.com/a/44371190/5741172

reduce-reducers's People

Contributors

acdlite avatar danawoodman avatar mjhm avatar rumyantsevmichael avatar smrq avatar suzdalnitski avatar timche avatar tuuling 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

reduce-reducers's Issues

warn whenever reducer overwrite props from previous reducers

at first awesome work @acdlite!

given the following case:

const reducerA  = (state = {}, action) => ({...state, { foo: 'bar' }});
const reducerB  = (state = {}, action) => ({...state, { foo: 'bazzzz': meh: 1, }});
reduceReducers(reducerA, reducerB)();  // { foo: 'bazzzz', meh: 1 }

in this case reducerA and reducerB uses the same key foo,
and since both will get merged in the end, the one on the right (reducerB) will
overwrite the value from the previous reducer.

I think can get confusing/hard to track

do you think would be helpful to have a warning (or even a validation) that shows up whenever
reducers with the same keys get combined?

just and idea, hope I make it clear enough for you to understand :)

Typing error with 1.0.3

Sorry to bring bad news again.

tsconfig.json and index.ts same as #30, new error:

node_modules/reduce-reducers/index.d.ts:7:1 - error TS1046: A 'declare' modifier is required for a top level declaration in a .d.ts file.

7 function reduceReducers<S>(initialState: S | null, ...reducers: Reducer<S>[]): Reducer<S>;
  ~~~~~~~~


Found 1 error.

Support initial state

Currently the code doesn't support initial state for the second reducer.
What do you think about the following solutions?

export function reduceReducers(...reducers) {
    return (state, action) => {
        let isInitial = !state;
        return reducers.reduce((newState, reducer) => {
            if (isInitial) {
                return { ...newState, ...reducer(undefined, action) };
            } else {
                return reducer(newState, action);
            }
        }, { ...state });
    };
}

or

export function reduceReducers(initialState, ...reducers) {
    return (state, action) => {
        let isInitial = !state;
        return reducers.reduce((newState, reducer) => {
            return reducer(newState, action);
        }, isInitial ? initialState : { ...state });
    };
}

Easier npm import

Normally, at least in VS Code, I can import something by clicking ctrl + .

This does not work with reduce-reducers. Instead I have to manually write

import * as reduceReducers from 'reduce-reducers';

If possible, make it so clicking ctrl + . on reduceReducers() automatically adds the import

import { reduceReducers } from 'reduce-reducers';

Thanks for this great utility function :)

Update .babelrc or remove it

Hey,
I just tried using this in a babel v6 environment and it bit me in the *** because your .babelrc file doesn't comply with the new version.

Could you either update it or remove it entirely? Thank you!

require from nodejs and webpack are not similar

Consider the following code:

var reduceReducers = require('reduce-reducers');
console.log(reduceReducers.default);

Running this code in node.js with node index.js, the output is undefined.

When processed with webpack with the following configuration:

module.exports = {
    mode: 'development',
    entry: {
        index: './index.js'
    },
    output: {
        filename: 'index.js'
    }
};

And executed in node.js with node dist/index.js, then the output is [Function].

In the file lib/index.js, replacing module.exports = exports['default'] with module.exports = exports makes the whole thing work.

Am I missing something?

Consider to change API

It'll be better to provide the initialState as the first argument. Otherwise it's impossible to declare proper typings (both for Typescript and Flow), cause rest parameters (list of reducers in the case) must be at the end of parameters list.

Using reducers from an external library

Hope I may ask for some assistance here. I'm using the redux-query library which comes with the following 2 reducers.

import { entitiesReducer, queriesReducer } from 'redux-query';

export const rootReducer = combineReducers({
  entities: entitiesReducer,
  queries: queriesReducer,
});

Works fine, entitiesReducer dynamically fills entities (with e.g. projects and users) after executing network requests.

I would however like to create the store with the following initial state so I can keep my selectors pure.

  entities: {
    projects: {
      allIds: [],
    }
  },

I have tried about every combination I could think of. Could this be possible using reduce-reducers?

Similarity with compose and better support for initialState

Hey,

Sorry to bring this issue up, because it's already too late to do anything about it, but I thought it still might be interesting for you.

I had the same problem with reducing reducers, but I made my own solution in form of composeReducers, which works just like redux compose, but passes two arguments, one of which is modified along the chain (i.e. state).

I knew this is a very generic pattern, so I came up with this:

export function compose(...funcs) {
  if (funcs.length === 0) {
    return x => x;
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (value, ...rest) => a(b(value, ...rest), ...rest));
}

Of course, in comparison with reduceReducers, you can't pass an initialState, but you can pass an initialStateReducer which initializes state for you. Also, execution direction is reversed - it goes from right to left (as in composition), instead of left to right (as in a chain or pipeline).

Since this pattern is very generic, you can compose not only reducers, but also string sanitizers, data parsers... hell, it's even compatible with redux store enhancers.

And some thoughts about initialState support: In my opinion, the best way to support initialState is:

reduceReducers(reducers = [], initialState = undefined)

This way, you're simplifying the function signature (think of using this in typescript), it is similar to Array.prototype.reduce(reducer, initialValue), and you give users the liberty to not use the initialState.

Right now, it forces you to write null, which breaks the assumption that uninitialized state is undefined, and the function signature is just weird.

So yeah, that's all I wanted to say, would be interesting to hear your thoughts.

EDIT: I've updated the implementation to use functional style, as in the current redux compose implementation.

Incorrect TypeScript declaration

TypeScript type declaration created in #27 is incorrect. It doesn't allow combining of multiple reducers without an initial state.

Both examples below are taken from tests (with some modifications).

const initialState = {A: 0, B: 0};
type State = typeof initialState;

// this one is OK
const reducer1 = reduceReducer(initialState, (state: State, action: Action) => ({
  ...state,
  A: state.A + 1
}));

// this one fails because first argument is expected to be initial state
const reducer2 = reduceReducer(
  (state: State, action: Action) => ({...state, A: state.A + 2}),
  (state: State, action: Action) => ({...state, B: state.B * 2}),
);

The second example fails with the following error:

Argument of type '(state: { A: number; B: number; }, action: Action) => { B: number; A: number; }' is not assignable to parameter of type 'Reducer<(state: { A: number; B: number; }, action: Action) => { A: number; B: number; }>'.
Types of parameters 'state' and 'state' are incompatible.
Type '(state: { A: number; B: number; }, action: Action) => { A: number; B: number; }' is missing the following properties from type '{ A: number; B: number; }': A, B
Link to TypeScript Playground

https://www.typescriptlang.org/play/index.html#src=%2F%2F%20type%20declarations%2C%20copy%20from%20https%3A%2F%2Fgithub.com%2Fredux-utilities%2Freduce-reducers%2Fblob%2Fmaster%2Findex.d.ts%0D%0Atype%20Action%20%3D%20%7B%0D%0A%20%20type%3A%20string%3B%0D%0A%7D%3B%0D%0A%0D%0Atype%20Reducer%3CS%3E%20%3D%20(state%3A%20S%2C%20action%3A%20Action)%20%3D%3E%20S%3B%0D%0A%0D%0Adeclare%20function%20reduceReducer%3CS%3E(%0D%0A%20%20initialState%3A%20S%2C%0D%0A%20%20...reducers%3A%20Array%3CReducer%3CS%3E%3E%0D%0A)%3A%20Reducer%3CS%3E%3B%0D%0A%0D%0A%2F%2F%20reduce-reducers%20use%2C%20modified%20examples%20from%20https%3A%2F%2Fgithub.com%2Fredux-utilities%2Freduce-reducers%2Fblob%2Fmaster%2Ftest%2Findex.test.js%0D%0Aconst%20initialState%20%3D%20%7BA%3A%200%2C%20B%3A%200%7D%3B%0D%0Atype%20State%20%3D%20typeof%20initialState%3B%0D%0A%0D%0A%2F%2F%20this%20one%20is%20OK%0D%0Aconst%20reducer1%20%3D%20reduceReducer(initialState%2C%20(state%3A%20State%2C%20action%3A%20Action)%20%3D%3E%20(%7B%0D%0A%20%20...state%2C%0D%0A%20%20A%3A%20state.A%20%2B%201%0D%0A%7D))%3B%0D%0A%0D%0A%2F%2F%20this%20one%20fails%20because%20first%20argument%20is%20expected%20to%20be%20initial%20state%0D%0Aconst%20reducer2%20%3D%20reduceReducer(%0D%0A%20%20(state%3A%20State%2C%20action%3A%20Action)%20%3D%3E%20(%7B...state%2C%20A%3A%20state.A%20%2B%202%7D)%2C%0D%0A%20%20(state%3A%20State%2C%20action%3A%20Action)%20%3D%3E%20(%7B...state%2C%20B%3A%20state.B%20*%202%7D)%2C%0D%0A)%3B%0D%0A%0D%0A%2F%2F%20above%20error%3A%0D%0A%2F%2F%20Argument%20of%20type%20'(state%3A%20%7B%20A%3A%20number%3B%20B%3A%20number%3B%20%7D%2C%20action%3A%20Action)%20%3D%3E%20%7B%20B%3A%20number%3B%20A%3A%20number%3B%20%7D'%20is%20not%20assignable%20to%20parameter%20of%20type%20'Reducer%3C(state%3A%20%7B%20A%3A%20number%3B%20B%3A%20number%3B%20%7D%2C%20action%3A%20Action)%20%3D%3E%20%7B%20A%3A%20number%3B%20B%3A%20number%3B%20%7D%3E'.%0D%0A%2F%2F%20Types%20of%20parameters%20'state'%20and%20'state'%20are%20incompatible.%0D%0A%2F%2F%20Type%20'(state%3A%20%7B%20A%3A%20number%3B%20B%3A%20number%3B%20%7D%2C%20action%3A%20Action)%20%3D%3E%20%7B%20A%3A%20number%3B%20B%3A%20number%3B%20%7D'%20is%20missing%20the%20following%20properties%20from%20type%20'%7B%20A%3A%20number%3B%20B%3A%20number%3B%20%7D'%3A%20A%2C%20B%0D%0A

Typing error with 1.0.2

Error:

node_modules/reduce-reducers/index.d.ts:7:1 - error TS1046: A 'declare' modifier is required for a top level declaration in a .d.ts file.

7 function reduceReducers<S>(initialState: S | null, ...reducers: Reducer<S>[]): Reducer<S>;
  ~~~~~~~~

node_modules/reduce-reducers/index.d.ts:10:16 - error TS2552: Cannot find name 'reduceReducer'. Did you mean 'reduceReducers'?

10 export default reduceReducer;
                  ~~~~~~~~~~~~~

  node_modules/reduce-reducers/index.d.ts:7:10
    7 function reduceReducers<S>(initialState: S | null, ...reducers: Reducer<S>[]): Reducer<S>;
               ~~~~~~~~~~~~~~
    'reduceReducers' is declared here.


Found 2 errors.

index.ts:

import reduceReducers from 'reduce-reducers'

tsconfig.json:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "lib": ["es5", "dom"],
    "jsx": "react",
    "declaration": true,
    "declarationDir": "dist",
    "outDir": "dist",
    "strict": true,
    "allowSyntheticDefaultImports": true
  }
}

TypeScript: 3.4.3

Type declaration doesn't allow for none default action types

if the reducer uses a type for action that isn't the base redux Action typescript can't resolve what overload to use

given the reducer

import reduceReducers from 'reduce-reducers';
import { Action } from 'redux';

const ADD = 'ADD';
const MUL = 'MUL';

interface State {
  value: number;
}

interface AddAction extends Action<typeof ADD> {
  value: number;
}

interface MulAction extends Action<typeof MUL> {
  value: number;
}

const initialState: State = { value: 0 };

const addReducer = (state: State, action: AddAction): State => {
  switch (action.type) {
    case ADD: {
      return { value: state.value + action.value };
    }
    default: {
      return state;
    }
  }
};

const mulReducer = (state: State, action: MulAction): State => {
  switch (action.type) {
    case MUL: {
      return { value: state.value + action.value };
    }
    default: {
      return state;
    }
  }
};

const reducer = reduceReducers(initialState, addReducer, mulReducer);

typescript reports on initialState
Argument of type 'State' is not assignable to parameter of type 'Reducer<State>'. Type 'State' provides no match for the signature '(state: State, action: Action): State'.ts(2345)

even changing the AddAction & MulAction interface to

interface AddAction {
  type: string;
  value: number;
}

interface MulAction {
  type: string;
  value: number;
}

results in the same error

i haven't found a solution to the types but a workaround is to cast the reducers to the default reducer

const reducer = reduceReducers(initialState, addReducer as Reducer<State>, mulReducer as Reducer<State>);

this was tested in typescript 3.5.0 3.5.2 & 3.5.3
typescript doesn't report the error with "strict"=false

reduce reducer closure method is getting call intial time

My application is running properly with dev mode, but when i am running application in npm build mode (i,e debugMode true) reduce reducer is not working.

import reduceReducers from 'reduce-reducers';
My reducer code
export const createReducer = reduceReducers( userReducer, initialState);

Module.ts
@NgModule({
declarations: [],
imports: [
CommonModule,
StoreModule.forRoot({ userData: createReducer }),
EffectsModule.forRoot(effects)
],
exports: [],
providers: []
})

After debugging in prod i found reduceReducers( userReducer, initialState) is returning undefined where as in dev mode it returns function.

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.