-
-
Notifications
You must be signed in to change notification settings - Fork 859
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Immer 7 #603
Immer 7 #603
Conversation
Hmm. We do show some examples kinda like this in our current "basic tutorial", to introduce the idea: const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1
}
}) Obviously that doesn't take any actual advantage of Immer, but it's entirely valid, and it's getting the action creators generated for free as usual. I don't expect that most real code does that, but having legal reducers like that stop working seems problematic. What's the impetus for the change in behavior? |
@markerikson there has been quite some confusion in the past where things were passed into produce and Immer didn't seem to work, because the thing passed wasn't draftable, e.g. a class that holds state, rather than a plain object (in which case it should have been marked immerable). I suspect that you wrap all the reducer functions in RKJS with some other function? Making that conditional should make sure existing patterns keep working, a bit like |
BREAKING CHANGE: `produce` no longer accepts any base state to operate on that is not draftable, something which caused quite some confusion in the past. If you were using `produce` in a way where it might be invoked on non draftable data, you need to guard it with `isDraftable` from now before invoking the producer.
Yeah, here's our current implementation as of v1.3.x: export function createReducer<S>(
initialState: S,
mapOrBuilderCallback:
| CaseReducers<S, any>
| ((builder: ActionReducerMapBuilder<S>) => void)
): Reducer<S> {
let actionsMap =
typeof mapOrBuilderCallback === 'function'
? executeReducerBuilderCallback(mapOrBuilderCallback)
: mapOrBuilderCallback
return function(state = initialState, action): S {
const caseReducer = actionsMap[action.type]
if (caseReducer) {
if (isDraft(state)) {
// we must already be inside a `createNextState` call, likely because
// this is being wrapped in `createReducer`, `createSlice`, or nested
// inside an existing draft. It's safe to just pass the draft to the mutator.
const draft = state as Draft<S> // We can assume this is already a draft
return caseReducer(draft, action) || state
} else {
// @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>) => {
return caseReducer(draft, action)
})
}
}
return state
}
} So we could presumably add another condition in there or something. In fact, maybe we add a second condition to the |
yeah correct, that should do the trick :) (that would even be a forward &
backward compatible change)
…On Sat, May 23, 2020 at 6:00 PM Mark Erikson ***@***.***> wrote:
Yeah, here's our current implementation as of v1.3.x:
export function createReducer<S>(
initialState: S,
mapOrBuilderCallback:
| CaseReducers<S, any>
| ((builder: ActionReducerMapBuilder<S>) => void)): Reducer<S> {
let actionsMap =
typeof mapOrBuilderCallback === 'function'
? executeReducerBuilderCallback(mapOrBuilderCallback)
: mapOrBuilderCallback
return function(state = initialState, action): S {
const caseReducer = actionsMap[action.type]
if (caseReducer) {
if (isDraft(state)) {
// we must already be inside a `createNextState` call, likely because
// this is being wrapped in `createReducer`, `createSlice`, or nested
// inside an existing draft. It's safe to just pass the draft to the mutator.
const draft = state as Draft<S> // We can assume this is already a draft
return caseReducer(draft, action) || state
} else {
// @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>) => {
return caseReducer(draft, action)
})
}
}
return state
}}
So we could presumably add another condition in there or something. In
fact, maybe we add a second condition to the isDraft() clause?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#603 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBAH6L2BI6HUCKELPSLRS76LDANCNFSM4NIBGG5A>
.
|
actually, we can preserve the current behavior for primitives I guess, it is mostly about passing some undraftable object into produce which is a pitfall. |
+10000 for being able to log Proxy's and see actual data. Some situations can be really hard to debug without this. If it's a perf thing, just show Proxy data with |
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 7bb29d5:
|
🎉 This PR is included in version 7.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Planned changes:
produce
. CC @markerikson do you see any potential problems here?current
state copy of a draft, for logging purposes etc?original
return plain values as-is? #605