Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow combineReducers reducers to consult global state #2750

Closed
entropitor opened this issue Dec 6, 2017 · 6 comments
Closed

Allow combineReducers reducers to consult global state #2750

entropitor opened this issue Dec 6, 2017 · 6 comments

Comments

@entropitor
Copy link

Do you want to request a feature or report a bug?
feature

What is the current behavior?
The subreducers of combineReducers are real reducers, they only get state and action, it would be nice if we could pass an extra argument to these reducers through an option, e.g. to inject the full state back into the subreducers if a reducer might need some information of another reducer without ever needing to change it.

I know we can write this ourselves, however, we wouldn't get all the warnings and/or optimizations of combineReducers then.

e.g.

function bar (barState = 4, action) {
  if (action.type === 'ODD') return 3
  else return 4
}
function foo (fooState = 0, action, fullState) {
  return fooState + fullState.bar
}
combineReducers({
  foo,
  bar,
}, {
  extraArg: state => state,
})
@timdorr timdorr changed the title Option to pass extra argument to combineReducers subreducer Allow combineReducers reducers to consult global state Dec 6, 2017
@timdorr
Copy link
Member

timdorr commented Dec 6, 2017

This is related to #1768, which was abandoned.

@markerikson
Copy link
Contributor

I know that Dan's opinion on this is that combineReducers is simple enough conceptually that people should just write their own reimplementation if they need any different behavior.

@reduxjs reduxjs deleted a comment from mileschristian Mar 9, 2018
@langpavel
Copy link

I think that this may break boundaries of redux state — you may do this itself but you should not request this from this library…

@insidewhy
Copy link

insidewhy commented May 6, 2018

Here's a typesafe version I wrote for typescript 2.8:

type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never

type CombinedState<T extends object> = { [K in keyof T]: FunctionReturnType<T[K]> }

type ActionFromReducer<T extends object> = T[keyof T] extends (
  state: any,
  action: infer A,
  ...args: any[]
) => any
  ? A
  : never

type ReducerReturnValue<T extends object, R> = (
  state: CombinedState<T>,
  action: ActionFromReducer<T>,
  rootState: R,
) => CombinedState<T>

// TODO: remove inner function, see:
//  * https://github.com/Microsoft/TypeScript/issues/20122
//  * https://github.com/Microsoft/TypeScript/pull/23696
export function combineReducers<R>() {
  return <T extends object>(reducers: T): ReducerReturnValue<T, R> => {
    type S = CombinedState<T>
    const reducerProps = Object.keys(reducers)

    return (state: S = {} as S, action: any, rootState: R): S => {
      let hasChanged = false
      const nextState: S = {} as S
      for (let i = 0; i < reducerProps.length; ++i) {
        const prop = reducerProps[i]
        const reducer = reducers[prop]
        const previousStateForKey = state[prop]
        const nextStateForKey = reducer(previousStateForKey, action, rootState)
        nextState[prop] = nextStateForKey
        hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      }
      return hasChanged ? nextState : state
    }
  }
}

type ReducerReturnValueRoot<T extends object> = (
  state: CombinedState<T>,
  action: ActionFromReducer<T>,
) => CombinedState<T>

export function combineReducersRoot<T extends object>(reducers: T): ReducerReturnValueRoot<T> {
  type S = CombinedState<T>
  const reducerProps = Object.keys(reducers)

  return (state: S = {} as S, action: any): S => {
    let hasChanged = false
    const nextState: S = {} as S
    for (let i = 0; i < reducerProps.length; ++i) {
      const prop = reducerProps[i]
      const reducer = reducers[prop]
      const previousStateForKey = state[prop]
      const nextStateForKey = reducer(previousStateForKey, action, state)
      nextState[prop] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

You have to use combineReducersRoot(reducerMap) for the root reducer and combineReducers<State>()(reducerMap) for non-root reducers due to a limitation with typescript (see microsoft/TypeScript#20122).

@passabilities
Copy link

Why not pass the global state as an argument to the reducers?

I created a PR (#3010) to show example.

@markerikson
Copy link
Contributor

Still a nice idea, but realistically, this isn't going to happen at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants