How to dynamically add a new property while updating the draft on immer?
Asked Answered
D

3

6

Consider the following code, where line 2 fails with Property 'newProperty' does not exist on type 'WritableDraft<MyObject>'. TS7053

// data is of type MyObject which until now has only a property myNumber
const payload = produce(data, (draft) => {    
  draft['newProperty'] = 'test';              // Property 'newProperty' does not exist on type 'WritableDraft<MyObject>'.  TS7053
});                                           

How can I dynamically add a new property to the draft or change the type of the draft to a type which already includes the newProperty? I do not want to have newProperty in the MyObject type itself.

Dieselelectric answered 27/2, 2021 at 14:49 Comment(4)
This is not a great solution, but couldn't you just cast it to any?Torbert
Yes, that works, thanks. Yet I am wondering if there is any better / official way to do it?Dieselelectric
#12711405, I pretty sure you might want to take a look at this one. In case if you need to add any new property even in a for loop. This question is quite common in fact but all solutions seem like just defeats the original purpose of typescript. I don't think there is a truly elegant way to do it. Otherwise you just need to add a new property as a interface for the parameter.Inquiline
You seem to want to operate like a simple dictionary. The question is ofcourse, what will you do with that object afterwards, and does it make sense (the code you are showing here doesn't offer any benefit to a consumer afterwards, they are not aware it now has new properties, so what is your end goal)Naevus
C
2

If you want to keep this new type on payload you will have to cast new type on parameter data:

let payload = produce(data as MyObject & { newProperty: string }, (draft) => {
  draft["newProperty"] = "test";
})

If you are not interested in keeping your type on payload, you can modify type of draft either in parameter of function, or inside: (draft as MyObject & { newProperty: string })[newProperty] = "test"

Circumspect answered 11/3, 2021 at 20:59 Comment(0)
H
1

You can add new properties to preexisting types by using the union operator: &. The code below should solve your problem.

(draft: WritableDraft<MyObject> & { newProperty : string }) => {
  // put the rest of your code here
}

Edit

After looking at Linda Paiste's answer, I think there are a few issues with the approach I took above. My answer does add a property to the type, which removes any error you would get from accessing or modifying the newProperty attribute in the function. However, it is more verbose and difficult to maintain if types or variables names are changed in the future. Additionally, it forces the caller of the function to pass in an object that contains the newProperty property, which is likely not what you want.

Helicograph answered 11/3, 2021 at 20:46 Comment(2)
I think {newProperty: string} should go into <>, so it would be WritableDraft<MyObject & { newProperty : string }>Circumspect
@ezhikov You might be right, but I can't tell because the definition for WritableDraft isn't provided.Helicograph
S
1

It's actually fine to return a new object from produce as long as you don't also modify the draft. The docs on returning new data from producers show an example with a switch where some cases modify the draft and others return a new state. That is fine as long as you don't do do both in the same case.

const payload = produce(data, (draft) => {
    return { ...draft, newProperty: "test" };
});

const n = payload.myNumber; // number
const s = payload.newProperty; // string

Pros:

  • No as assertions needed
  • payload gets correct type { newProperty: string; myNumber: number; }

Cons:

  • Doesn't actually make use of Immer's capabilities

Typescript Playground Link

Scriptural answered 12/3, 2021 at 22:42 Comment(2)
No longer works as TS 3.7.5Leveroni
Interesting! It definitely used to work when I wrote this answer. I suspect that the change is probably in the immer package types rather than in TS core, but I'll have to look into it.Scriptural

© 2022 - 2024 — McMap. All rights reserved.