immerjs / immer

Create the next immutable state by mutating the current one

Home Page:https://immerjs.github.io/immer/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use Immer Reducer to create Composite Object

Wonko-Da-Sane opened this issue Β· comments

πŸ™‹β€β™‚ Question

I am attempting to use an Immer Reducer to create a composite object. Basically, I need to load some data from the database, and when it's loaded, load additional data to extend that object. In the demo, it's using hard-coded data and a sleep to simulate that loading.

It appears to be setting the first set of data properly.

console.log("Before set: " + draft.players)
    draft.players = action.data
    console.log("After set: " + draft.players)
    draft.players.forEach((p) => console.log(p))

But when the second set of data is loaded, I cannot find the previously set objects to update.

let player = draft.players.find(
                    (p) => p.playerId === hitterData.playerId
                )
                if (player) {
                    console.log(
                        "Updating " + player.playerAndTeamInfo.playerName
                    )
                    player.hitterStats = hitterData
                } else {
                    console.log(
                        "Could not find player id " + hitterData.playerId
                    )
                }

I would expect the draft.players object to be the array that I added earlier. The first log message returns what I would expect the length of that array to be. However, it is a Proxy(Array) and each item a Proxy(Object), but not the data I'm expecting. It's probably just my misunderstanding of the Immer Reducer syntax, but I've been stuck here for a while.

Link to repro

CodeSandbox demo

Environment

  • Immer version:
  • Immer 10.0.4
  • use-immer 0.9.0

More Info?

If you need more information, please comment below with what you'd like me to add

Sorry that is a bit too much code in your reproduction to understand first to be able to help with. Could you narrow down your question in a minimal example first and highlight what you expect where?

Ha - that was an attempt at a cut down version.

Basically, I have some data that I want to load in stages. So, at first, I want to load a list of players. Each player will have an id, a name, and a team. The loading function calls the reducer and creates the initial list in state.

case "ReducerConstants.LOADED_PLAYER_STATE":
        draft.players = action.data;
        break;

Once that data is loaded, additional data is loaded for the stats for the players. The stats for an individual player are then sent to the reducer, and I want to get find the player from that players array by id.

      case "ReducerConstants.UPDATE_PLAYER_WITH_HITTER_DATA":
        let hitterData = action.data;
        let player = draft.players.find(
          (p) => p.playerId === hitterData.playerId
        );
        if (player) {
          console.log("Updating " + player.playerAndTeamInfo.playerName);
          player.hitterStats = hitterData;
        } else {
          console.log("Could not find player id " + hitterData.playerId);
        }
        break;

So, the issue I have is the find in the second reducer case. I was expecting draft.players to be the same array that I had set in the first call to the reducer. I always hit the "else" case, because that list appears to be a list of Proxy(Object) and not a player object.

@Wonko-Da-Sane I'm also rather confused by what the actual problem is.

Yes, Immer wraps all values inside of Proxies - that's how it works. (If you were to introspect the values inside the proxy, you'd ultimately see the original plain data object.)

The logic you're showing doesn't seem to match your description of what's happening. The only way you'd end up inside of the else block is if players.find() isn't returning anything and instead returns undefined. If it did actually find a match, it would end up inside the if (player) block, regardless of whether player is a plain JS object or a Proxy object.

Looking at your original code sample, it looks like you have a logic error / data mismatch.

Your data is structured as:

{
  data: [
        {
          playerAndTeamInfo: {
            playerId: 504,
            playerName: "Fernando Abad",
            lastName: "Abad",
            alternateSpelling: null,
            teamName: "Colorado Rockies",
            initials: "COL",
            mlbteamId: 9,
            startDate: "2023-01-05",
            endDate: null,
            boundedDateId: 35418,
          },
          hitterStats: null,
        },
    ]
}

So, to access the playerId field, you need item.playerAndTeamInfo.playerId.

But, your reducer logic is trying to compare vs (p) => p.playerId === hitterData.playerId - it's missing the .playerAndTeamInfo nesting. So yeah, it isn't finding a matching value, because the check is wrong.

Oh my goodness. That was exactly it - I missed the forest because I wsa too focused on figuring out the shiny new trees I am trying to learn about.

I appreciate it greatly!

@Wonko-Da-Sane gotcha :)

A couple notes:

  • TypeScript would have caught this
  • You might also want to consider using Redux Toolkit's createSlice to write the reducer function for use with the useReducer hook, as it already has Immer built in and may simplify some of the setup