Open karlvr opened 5 years ago
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.
@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!
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)),
)
}
I'm introducing immer https://github.com/mweststrate/immer to our project, in which we use your fantastic module.
We currently use this pattern:
And I would love to make this simpler. Something like...
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 :-)