-
-
Notifications
You must be signed in to change notification settings - Fork 842
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
Typescript initialization without change #381
Comments
Just declare the members as readonly? Or use the ReadOnly utility?
Op do 6 jun. 2019 10:33 schreef dexx086 <notifications@github.com>:
… First of all, thanks for this great library, I already love immer :)
But one thing bothers me, I hope someone can help.
What I'm trying to do: initialize an object as immutable but *without*
any real property changes. Not the object instance is important for me (so
the fact that it's not 'freezed' doesn't matter, because I know, in
production it's already turned off by default). The only thing is important
for me: *to make Typescript catch any mistaken direct changes* to the
properties after I 'made it immutable'.
But the strange thing is that: if I give an empty producer in calling
'produce' (CASE 1), after that Typescript allows to directly modify
property of the returned 'immutable' object (what I don't want, and don't
understand why)!
But...if I do any simple modification in the producer (CASE 2), Typescript
will not allow direct changes on the properties of the returned object (as
expected).
Why (and how) Typescript differentiates between the 'empty' and the
'really modifying' producer? How could I initialize the immutable object
without any real changes but making Typescript after that to catch when
trying to modify readonly properties?
Thanks in advance.
import produce, { immerable } from "immer";
class TheClass {
[immerable] = true;
public title: string = "";
}
// Just initialize the objectlet data = new TheClass();// Want to make it IMMUTABLE from this point, to avoid any direct property changes
// CASE 1let immutable1 = produce(data, (draft) => { /* empty, dont want to change anything */ });// Typescript should NOT allow this, it should be immutable with readonly propertiesimmutable1.title = "shouldn't be able to modify..."; // But, it works....
// CASE 2let immutable2 = produce(data, (draft) => { draft.title = "initial modification"; });// Error (as expected): Cannot assign to read only property...immutable2.title = "it won't work, Typescript catches it";
codesandbox
<https://codesandbox.io/s/empty-wildflower-l873t?autoresize=1&expanddevtools=1&fontsize=14&module=%2Fsrc%2Findex.ts>
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#381?email_source=notifications&email_token=AAN4NBCQT4MEF2FMZOIIURDPZDDULA5CNFSM4HU6KXHKYY3PNVWWK3TUL52HS4DFUVEXG43VMWVGG33NNVSW45C7NFSM4GX6WNYQ>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAN4NBGUBGY5CC2RC2PHTADPZDDULANCNFSM4HU6KXHA>
.
|
I don't want to make the properties readonly, I just want to 'initialize' the object (without actually changing it) to be an immer's immutable object! But for some reason, if I don't really modify a property inside the producer, the returned object is not handled as immutable in Typescript. Do you see why the two producers (empty / actual change) causes different types for the returned objects in Typescript? Because it's my only problem... CASE 2 is what I'd like to achieve but without the modification... |
Well, it turned out that the error message was not a compile error... the IDE on codesandbox highlighted it and it seemed like this was a type checking error at compile time, but actually it was a runtime error. The IDE just (btw cleverly but a bit confusingly) highlighted the errorneous line (even from runtime error)... So, it has nothing to do with Typescript, it's not a compile error, so it's not type-checkable if the class is really an immutable object, or still the original mutable one. I guess need to make sure for such cases to have the initial object somehow modified in the producer to force making it really an immutable one... |
If you really want your objects frozen at runtime, you should use Immer will only freeze objects that were changed by the producer. |
You're right, I could manually freeze it with other tools, but as using Immer, would be much practical to use that for this case as well (all I'm looking for is a 'force freeze' funcionality). I think my point about this whole thing is not clear, let me describe a usecase:
It will be stay hidden if unluckily this is the first modification, and happens only once. This is why I'm looking for a bulletproof solution to make it (force) to be converted to immutable, and everywhere I'm using the immutable one, (kind-of) immediately turns out if a mistaken direct change stays in the code, because for the frozen object it will raise a (as turned out) runtime error. Btw: isn't it possible to have return types from |
Here's a solution that freezes any objects added to a draft: #260 (comment)
You can wrap import {produce} from 'immer'
import deepFreeze from 'deep-freeze'
export function produceFrozen(base, recipe) {
if (!Object.isFrozen(base)) deepFreeze(base)
const result = produce(base, recipe)
if (!Object.isFrozen(result)) deepFreeze(result)
return result
} Note: You should probably only use that when
I tried going that direction, and many people didn't like how |
Thanks for your detailed feedback. I checked the #289 thread you mentioned: yeah, I see it brought up some objections about the change. Although I agree with that it might be a big change and maybe could had waited for the next major version, but I disagree about that it should not be introduced at all. Typescript is all about type safety, if somewhere it raised up problems, that needs to be solved, but should accept the even more type- and usage safetyness that it would bring in. And imho it would not make Immer "opinnionated". But, I don't get why you idea with the Is your idea totally rejected, or do you see it viable to be introduced (in any way) in the near future? Meanwhile, I came up with a (little hacky and regarding future changes of Immer, possibly fragile) workaround solution based on your modification: a new 'produce' method. // Need a copy, it's not exported from Immer
declare class Nothing {
// This lets us do `Exclude<T, Nothing>`
private _: any;
}
// Need a copy, it's not exported from Immer
type FromNothing<T> = T extends Nothing ? undefined : T;
/** The inferred return type of `produce` with Immutable<Base> return type */
export type ProducedRO<Base, Return> = Return extends void
? Immutable<Base>
: Return extends Promise<infer Result>
? Promise<Result extends void ? Base : FromNothing<Result>>
: FromNothing<Return>;
function produceRO<Base, D = Draft<Base>, Return = void>(base: Base, recipe?: (draft: D) => Return, listener?: PatchListener): ProducedRO<Base, Return> {
let result = produce(base, recipe ? recipe : (): any => { }, listener);
// @ts-ignore
return result;
}
let data = new TheClass();
let immutable1 = produceRO(data);
immutable1.title = "won't work..."; // Typescript compile error as expected: Cannot assign to 'title' because it is a read-only property. |
Wow, didn't expect such a superfast response on my idea, especially not together with a commit! :) Thank you for your support. I hope introducing this option could be suitable for everbody. |
Yeah, PS: My PR isn't ready for primetime, since the |
Random idea; I think this could also be meta programmed in TS, such that
one could do `produce<"returnImmutable">()`, this could also be done to fix
problems with draft (having a Draft is not always the best option,
especially if the base state type was mutable already, e.g.:
`produce<"useDraft">` etc
…On Sat, Jun 8, 2019 at 2:38 PM Alec Larson ***@***.***> wrote:
Yeah, produceStrict might be a better name. I'm open to any suggestions.
*PS:* My PR isn't ready for primetime, since the SafeReturn type it adds
results in an incorrect type.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#381?email_source=notifications&email_token=AAN4NBAK2CVZUWIOIYAQUHTPZOR6BA5CNFSM4HU6KXHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXHT4DY#issuecomment-500121103>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAN4NBCWQ4CEGYBYZBHKKKTPZOR6BANCNFSM4HU6KXHA>
.
|
@mweststrate That is disgusting to me, but maybe...? 😆 |
5.3 will introduce an |
First of all, thanks for this great library, I already love immer :)
But one thing bothers me, I hope someone can help.
What I'm trying to do: initialize an object as immutable but without any real property changes. Not the object instance is important for me (so the fact that it's not 'freezed' doesn't matter, because I know, in production it's already turned off by default). The only thing is important for me: to make Typescript catch any mistaken direct changes to the properties after I 'made it immutable'.
But the strange thing is that: if I give an empty producer in calling 'produce' (CASE 1), after that Typescript allows to directly modify property of the returned 'immutable' object (what I don't want, and don't understand why)!
But...if I do any simple modification in the producer (CASE 2), Typescript will not allow direct changes on the properties of the returned object (as expected).
Why (and how) Typescript differentiates between the 'empty' and the 'really modifying' producer? How could I initialize the immutable object without any real changes but making Typescript after that to catch when trying to modify readonly properties?
Thanks in advance.
codesandbox
The text was updated successfully, but these errors were encountered: