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
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 theuseReducer
hook, as it already has Immer built in and may simplify some of the setup