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

Introduce create(Immer)Reducer utility? #31

Closed
mweststrate opened this issue Jan 4, 2018 · 5 comments
Closed

Introduce create(Immer)Reducer utility? #31

mweststrate opened this issue Jan 4, 2018 · 5 comments
Labels

Comments

@mweststrate
Copy link
Collaborator

the pattern is now typically:

function insertItem(array, action) {
    return immer(array, draft => {
        /* user code */ draft.splice(action.index, 0, action.item)
    })
}

So we could introduce a utility

createImmerReducer(fn: (draftState, action) => {})

which allows writing

const insertItem = createImmerReducer((draft, action) => {
    /* user code */ draft.splice(action.index, 0, action.item)
})

The implementation would roughly be:

function createImmerReducer(fn) {
  return (state, action) => immer(state, draft => fn(draft, action))
}
@mweststrate mweststrate changed the title Introduce createImmerReducer / createImmerReducers utilties Introduce create(Immer)Reducer utility? Jan 4, 2018
@hex13 hex13 mentioned this issue Jan 4, 2018
@marvinroger
Copy link

To handle the initial state, I'd rather do that:

export function createImmerReducer (initial, fn) {
  return (state = initial, action) => immer(state, draft => fn(draft, action))
}

That you can use like that:

const INITIAL_STATE = { foo: null }

const reducer = createImmerReducer(INITIAL_STATE, (draft, action) => {
  switch (action.type) {
    case 'SET_FOO':
      draft.foo = action.foo
  }
})

export default reducer

@hex13
Copy link

hex13 commented Jan 5, 2018

what with the reducers called by main reducer?

if I'm not mistaken it would be sufficient to just mark top-level reducer and rest of reducers could be called as normal functions?

function helperReducer(draft, action) {
     draft.foo = action.foo
}

const reducer = createImmerReducer(INITIAL_STATE, (draft, action) => {
  switch (action.type) {
    case 'SET_FOO':
         helperReducer(draft, action);
  }

@mpeyper
Copy link

mpeyper commented Jan 7, 2018

Personally, I would just go with createReducer as the import tells me it's an "immer" reducer

import { createReducer } from 'immer'

I would also like to see a few other utilities that help with redux interop and reusing as much of the redux ecosystem as possible:

  • combineMutators - combineReducers equivalent for immer mutators. Passes slices of the draft off to sub-mutators to handle

    function combineMutators(mutators) {
      return (draft, action) => {
        for (let key in mutators) {
          mutators[key](draft[key], action)
        }
      }
    }
  • createMutator - converts a redux reducer into an immer mutator (or whatever you want to call what will be passed to createReducer)

    function createMutator(reducer) {
      return (draft, action) => {
        const newDraft = reducer(draft, action)
        if (newDraft !== draft) {
          if (isPlainObject(draft)) {
            Object.assign(draft, newDraft)
          }
          if (Array.isArray(draft)) {
            draft.splice(0, draft.length, ...newDraft)
          }
        }
      }
    }

There is above are just some basic implementations of what I mean and probably wrong in 1000 different ways, but I hope you get the idea. You can definitely add a heap of safety and hinting code around them the help people out.

Would all this belong in the immer package, or would there be an redux-immer or immer-redux package created? Or perhaps import { createReducer } from immer/redux`?

@mweststrate
Copy link
Collaborator Author

mweststrate commented Jan 9, 2018

Implemented currying in 0.4.0, see https://github.com/mweststrate/immer#currying

createReducer is not really needed anymore because produce supports currying. So reducer = produce({ draft, action} => {}) does the trick now

I think combineMutators as proposed above is potentially a bit dangerous. It would be no problem from technical perspective, but it will mean that individual reducers will be hard to test again, which goes against the Redux philosophy. Instead, each individual reducer should be stand alone testable, and hence produce a new state.

Closing for now, I think the above patterns are after all so simple that they can easily be implemented in userland code. Let's have the current implementation settle a bit and wait until the dust is cleared :)

@salvoravida
Copy link
Contributor

https://github.com/salvoravida/redux-immer

another approach ...

Salvo

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

No branches or pull requests

5 participants