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

Support for immer #30

Open
karlvr opened this issue Mar 15, 2019 · 3 comments
Open

Support for immer #30

karlvr opened this issue Mar 15, 2019 · 3 comments

Comments

@karlvr
Copy link

karlvr commented Mar 15, 2019

I'm introducing immer https://github.com/mweststrate/immer to our project, in which we use your fantastic module.

We currently use this pattern:

reducer.case(actions.login.failed, (state, payload) => produce(state, draft => {
	draft.error = payload
	draft.loggingIn = false
}))

And I would love to make this simpler. Something like...

reducer.immerCase(actions.login.failed, (draft, payload) => {
	draft.error = payload
	draft.loggingIn = false
})

I'm not sure whether this idea fits in your module, as it's now specific to immer, and this library is about TypeScript and Redux...

If I'm right about the above, then I'm wondering out loud how to make this work... I think I'll start with a fork and see :-)

@dphilipson
Copy link
Owner

dphilipson commented Mar 16, 2019

Hi Karlvr!

First of all, thank you for telling me about Immer, which I had never seen and looks like a very useful library. I will definitely try it out in my own projects.

I would very much like to find a way to support your use case without adding a dependency on a specific library like Immer. I want to avoid specifically adding methods like .immerCase because it would force this library to take a dependency on Immer even for users who don't use this method, and I think it is a good thing for small libraries like this one to take on few or no dependencies whenever possible.

One thing you could do instead is use .withHandling and write yourself a reusable Immer helper. It would allow you to write code like this:

reducer.withHandling(immerCase(actions.login.failed, (draft, payload) => {
    draft.error = payload
    draft.loggingIn = false
})

where immerCase is defined as

function immerCase<S, P>(
    actionCreator: ActionCreator<P>,
    handler: (draft: S, payload: P) => void,
): (reducer: ReducerBuilder<S>) => ReducerBuilder<S> {
    return reducer =>
        reducer.case(actionCreator, (state, payload) =>
            produce(state, draft => handler(draft, payload)),
        );
}

Does that work for what you're trying to do?

An alternative I've considered before is to refactor the code to expose a ReducerBuilder class which could be extended to add new methods, but designing a class to be extensible adds a fair bit of complexity to the code and to testing, so I've been wary about doing this.

@karlvr
Copy link
Author

karlvr commented Mar 16, 2019

@dphilipson Thank you so much for your lightning fast response. I absolutely agree about dependencies etc.

My goal is to make the code simpler; I'm not sure whether the withHandling example is easier or harder than what I have now... although two =>s in a line is always a head scratcher!

Maybe if it was possible to register a handler wrapper we could make it work... but I'm not sure whether that would be able to integrate with the type system nicely.

I think in the meantime I'll make do with what I've got and see whether inspiration strikes!

@ivan-aksamentov
Copy link

ivan-aksamentov commented May 27, 2019

This is tremendous, folks! Last time I was so happy is probably only when I discovered the Redux itself. 🎉

The most important thing for me is that this way of writing reducers is a decent workaround for the years-old problem of return type inference (see #20 in this repo and numerous issues in typescript and redux repos). Because draft is based on mutating properties of an object, this problem just disappears.

Here is an updated version (changes to Draft<StateType> in the parameter types) to mitigate the type error:

import produce, { Draft } from 'immer'
import { ActionCreator } from 'typescript-fsa'
import { ReducerBuilder } from 'typescript-fsa-reducers'

export default function immerCase<StateType, PayloadType>(
  actionCreator: ActionCreator<PayloadType>,
  handler: (draft: Draft<StateType>, payload: PayloadType) => void,
): (reducer: ReducerBuilder<StateType>) => ReducerBuilder<StateType> {
  return reducer =>
    reducer.case(actionCreator, (state, payload) =>
      produce(state, draft => handler(draft, payload)),
    )
}

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

3 participants