3

I'm starting with Immer.js for immutability in JS and I can't find a way to remove an object in an array using the filter method. It returns the same object. BTW, I'm doing it in React with state but to make it more straightforward I made up simple snippets that reflect my problem.

const sampleArr = [
      {
        items: [
          { slug: "prod1", qnty: 1 },
          { slug: "prod2", qnty: 3 },
          { slug: "prod3", qnty: 2 },
        ],
      },
    ];
    
    const newState = produce(sampleArr, (draft) => {
      draft[0].items.filter((item) => item.slug !== "prod1");
    });
    
    console.log(newState);

Console.log was supposed to give me the same whole array but without the first item. However, what I get is the same array without any change.

I googled it and searched on immer docs but couldn't find my answer. Immer.js docs about Array mutation => https://immerjs.github.io/immer/docs/update-patterns

Obs. To test it out on chrome dev tools you can copy-paste the immer lib (https://unpkg.com/immer@6.0.3/dist/immer.umd.production.min.js) and change produce method to immer.produce

Gabriel Linassi
  • 429
  • 7
  • 13

3 Answers3

2

Using destructuring for making immutable objects goes against Immert. The way of solving this issue is by reassigning the filtered part to draft. The solution looks like this:

const sampleArr = [
  {
    items: [
      { slug: "prod1", qnty: 1 },
      { slug: "prod2", qnty: 3 },
      { slug: "prod3", qnty: 2 },
    ],
  },
];

const newState = produce(sampleArr, (draft) => {
  draft[0].items = draft[0].items.filter((item) => item.slug !== "prod1");
});

You can play around in the repleit here: https://replit.com/@GaborOttlik/stackoverflow-immer#index.js

0

Well, I ended up solving the problem on my own even though I'm not sure it's the right way.

What happens is that I need 2 things:

  1. A return from the produce function
  2. Copy the rest of the properties and add them to the new return

That said, if we write like this:

const newState = produce(sampleArr, (draft) => {
    return draft[0].items.filter((item) => item.slug !== "prod1");
});

We get back the filtered items array

[
    { slug: "prod2", qnty: 3 },
    { slug: "prod3", qnty: 2 },
]

However, it's required to add back the rest of the properties. Suppose you have more data, etc. So I did like this:

const newState = produce(sampleArr, (draft) => {
    draft = [
        ...draft.slice(0,0),
        {
            ...draft[0],
            draft[0].items.filter((item) => item.slug !== "prod1"),
        }
        ...draft.slice(1)
    ];
    return draft;
});

EDIT =================================================

Found out it's not required to do all that I did above. I could've done the way I did first. Was just lacking an assignment.

draft[0].items = draft[0].items.filter((item) => item.slug !== "prod1");

Gabriel Linassi
  • 429
  • 7
  • 13
0

The problem you're running into is that Immer doesn't allow you to both modify a draft and return a completely new value from the produce. Doing so will produce the following error discussed in further detail under this question:

Error: An immer producer returned a new value and modified its draft. Either return a new value or modify the draft

As pure speculation I would guess this is intentionally disallowed because this would almost always be a bug, except for this specific use-case.

To sidestep the problem, what you can do is wrap the array you want to work with in a temporary object and then destruct that object as you retrieve the result:

const { newArray } = produce({ newArray: oldArray }, (draft) => {
  // Filtering works as expected
  draft.newArray = draft.newArray.filter(...);

  // Modifying works as expected
  if (draft.newArray.length) {
    draft.newArray[0].nested.field = ...;
  }

  // No return statement
});
Etheryte
  • 24,589
  • 11
  • 71
  • 116