immerjs / immer

Create the next immutable state by mutating the current one
https://immerjs.github.io/immer/
MIT License
27.5k stars 850 forks source link

Typescript: Cannot assign immutable state from payload to immutable writable draft #1127

Closed gracicot closed 2 months ago

gracicot commented 2 months ago

🐛 Bug Report

Immer exposes a Immutable helper type that deeply mark a type as readonly:

type A = Immutable<{ a: number }>;

const a: A = {
  a: 4
};

a.a = 5; // error, a.a is readonly

Immer also conveniently make it so that a WritableDraft<...> will let you assign to subobjects since immer will not mutate the original state, which is correct:

const a2 = produce(a, (draft) => {
  // draft is WritableDraft<Immutable<...>>
  draft.a = 5;
});

However, writable draft don't let an immutable value be assigned to another immutable value:

type B = Immutable<{ arr: number[] }>;

const b1: B = {
  arr: [1, 2, 3]
};

const b2: B = {
  arr: [4, 5, 6]
};

produce(b1, draft => {
  draft.arr = b2.arr;
});

This is technically correct as arr will be assigned to another mutable value

This issue arises a lot when you have an immutable state in redux, use a selector and send back a state in an action payload. When action payloads are immutable, this issue will trigger.

Link to repro

A bug report without a reproduction is not a bug report. Failing to follow this templately is likely to result in an immediate close & lock of the issue.

CodeSandbox demo

To Reproduce

Steps to reproduce the behavior:

type B = Immutable<{ arr: number[] }>;

const b1: B = {
  arr: [1, 2, 3]
};

const b2: B = {
  arr: [4, 5, 6]
};

produce(b1, draft => {
  draft.arr = b2.arr;
});

Observed behavior

Typescript refuses the code

Expected behavior

Typescript should accept the code

Environment

We only accept bug reports against the latest Immer version.

mweststrate commented 2 months ago

This might help https://immerjs.github.io/immer/typescript#cast-utilities

On Thu, 13 Jun 2024, 16:54 Guillaume Racicot, @.***> wrote:

🐛 Bug Report

Immer exposes a Immutable helper type that deeply mark a type as readonly:

type A = Immutable<{ a: number }>; const a: A = { a: 4}; a.a = 5; // error, a.a is readonly

Immer also conveniently make it so that a WritableDraft<...> will let you assign to subobjects since immer will not mutate the original state, which is correct:

const a2 = produce(a, (draft) => { // draft is WritableDraft<Immutable<...>> draft.a = 5;});

However, writable draft don't let an immutable value be assigned to another immutable value:

type B = Immutable<{ arr: number[] }>; const b1: B = { arr: [1, 2, 3]}; const b2: B = { arr: [4, 5, 6]}; produce(b1, draft => { draft.arr = b2.arr;});

This is technically correct as arr will be assigned to another mutable value

This issue arises a lot when you have an immutable state in redux, use a selector and send back a state in an action payload. When action payloads are immutable, this issue will trigger. Link to repro

A bug report without a reproduction is not a bug report. Failing to follow this templately is likely to result in an immediate close & lock of the issue.

CodeSandbox demo https://codesandbox.io/p/sandbox/immer-sandbox-forked-t2n37v?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clxddfhdw00062e6e3lye9ae7%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clxddfhdw00022e6ehboiipb8%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clxddfhdw00032e6epk5fx4i7%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clxddfhdw00052e6efzk542jj%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clxddfhdw00022e6ehboiipb8%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxddfhdw00012e6ezo0k4uds%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Findex.ts%2522%252C%2522state%2522%253A%2522IDLE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A6%252C%2522startColumn%2522%253A1%252C%2522endLineNumber%2522%253A6%252C%2522endColumn%2522%253A1%257D%255D%257D%255D%252C%2522id%2522%253A%2522clxddfhdw00022e6ehboiipb8%2522%252C%2522activeTabId%2522%253A%2522clxddfhdw00012e6ezo0k4uds%2522%257D%252C%2522clxddfhdw00052e6efzk542jj%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxddfhdw00042e6ewmk74u5l%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clxddfhdw00052e6efzk542jj%2522%252C%2522activeTabId%2522%253A%2522clxddfhdw00042e6ewmk74u5l%2522%257D%252C%2522clxddfhdw00032e6epk5fx4i7%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522clxddfhdw00032e6epk5fx4i7%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Afalse%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D (typescript error don't show) To Reproduce

Steps to reproduce the behavior:

type B = Immutable<{ arr: number[] }>; const b1: B = { arr: [1, 2, 3]}; const b2: B = { arr: [4, 5, 6]}; produce(b1, draft => { draft.arr = b2.arr;});

Observed behavior

Typescript refuses the code Expected behavior

Typescript should accept the code Environment

We only accept bug reports against the latest Immer version.

  • Immer version:
  • I filed this report against the latest version of Immer
  • Occurs with setUseProxies(true)
  • Occurs with setUseProxies(false) (ES5 only)

— Reply to this email directly, view it on GitHub https://github.com/immerjs/immer/issues/1127, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAN4NBFY6V6UE5HH6M7XRTDZHGXCPAVCNFSM6AAAAABJISRW32VHI2DSMVQWIX3LMV43ASLTON2WKOZSGM2TCMZWGY2DEMQ . You are receiving this because you are subscribed to this thread.Message ID: @.***>

gracicot commented 2 months ago

It seems castDraft works. It still would be nice to not need that so I'll keep the issue open. I'm the case it is completely unfixable, then maintainers can close this issue.