Skip to content

RFC: Make immer optional for createReducer #242

@neurosnap

Description

@neurosnap

I think end-developers should be able to opt-out of immer behavior for createReducer and createSlice. There have been some questions raised recently after the 1.0 announcement that people have experienced performance issues when using createSlice because of immer.

For most use-cases, I think immer should be enabled. However, there are specific cases where I have needed to tune the performance of a set of reducers and needed to disable immer.

immer definitely provides a ton of value out-of-the-box and I think should be enabled by default. However, I also think this is one area where we can be a little more flexible and allow the option to disable it. createReducer is still extremely valuable even without immer and I think that is the main reason why I think it should be opt-out.

For createReducer we could provide an additional parameter that determines if immer is used:

 export function createReducer<
   S,
   CR extends CaseReducers<S, any> = CaseReducers<S, any>
- >(initialState: S, actionsMap: CR): Reducer<S> {
+ >(initialState: S, actionsMap: CR, useImmer: boolean = true): Reducer<S> {
   return function(state = initialState, action): S {
+    const caseReducer = actionsMap[action.type];
+    if (!useImmer) {
+      return caseReducer ? caseReducer(state, action) : state;
+    }
     // @ts-ignore createNextState() produces an Immutable<Draft<S>> rather
     // than an Immutable<S>, and TypeScript cannot find out how to reconcile
     // these two types.
     return createNextState(state, (draft: Draft<S>) => {
-     const caseReducer = actionsMap[action.type];
       return caseReducer ? caseReducer(draft, action) : undefined;
     });
   };
 }

Or we could create a separate function

export function createSimpleReducer<
  S,
  CR extends CaseReducers<S, any> = CaseReducers<S, any>
>(initialState: S, actionsMap: CR, useImmer: boolean = true): Reducer<S> {
  return function(state = initialState, action): S {
    const caseReducer = actionsMap[action.type];
    return caseReducer ? caseReducer(state, action) : state;
  };
}

I'm curious of everyone's thoughts. Can anyone else think of other ways to implement this feature?

I also think that createSlice should provide a property immer which gets passed to createReducer.

 const slice = createSlice({
   name: 'slice',
+  immer: false,
   // ...
 });

I could see this kind of change requiring some typings work before merging.

I'm willing to do the work to get a PR up for this, but I wanted to make sure it was something that the maintainers agree would be useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions