have a complex redux situation I am working with and cant quite get what I need. I have 2 objects, Track and Target
interface Track {
id: number,
...other fields
}
interface Target {
id: number (same as the Track)
tracks: Track[]
...other fields
}
what I am trying to do is when I fetch the tracks I am trying to see if there are any targets that have the same ID (one target to many tracks), and if it does then add the track to that target's tracks array, otherwise - create a new target with that Track id and put the track in the tracks array
case TargetActionTypes.FETCH_TARGETS_SUCCESS:
return {
...state,
targets: [
action.tracks.map((track: Track) =>
state.targets.map((target: Target) =>
track.target_id === target.id
? {
...target,
tracks: [...target.tracks, track]
}
: {
id: track.target_id,
visible: true,
tracks: [track]
}
)
)
]
}
I think this is on the right track but typescript is complaining because I think maybe its nesting things one level too deep because of the nested loops? Typescript errors here
Types of property 'targets' are incompatible. Type '{ tracks: Track[]; id: number; visible: boolean; }[][][]' is not assignable to type 'Target[]'.
Type '{ tracks: Track[]; id: number; visible: boolean; }[][]' is missing the following properties from type 'Target': id, visible, tracks",
As stated in comments, property targets in your state has type Target[], but what you are assigning to it is not. state.targets.map(...) returns an array of targets, so Target[], then it is returned from callback function to another map so action.tracks.map(...) returns an array of arrays of Target (so Target[][]) and then you are wrapping it in a pair of square brackets, so it becomes Target[][][]. Call to .map returns an array, so if you wrap it in square braces, you get array in array, so the first step is to remove those.
Now to get rid of another level of nesting you will need to get rid of .map, which won't quite work in this case, because .map just transforms one array to another array with the same length. Here the length can vary, so .map is not a very good fit. The simplest way is just not using built-in methods and going with a loop:
const targets: Target[] = []
for(const track of tracks) {
const target = targets.find(target => target.id === track.target_id)
if (target) target.push(track)
else targets.push({id: track.target_id, visible: true, tracks: [track]})
}
Maybe you could make this algorithm a little faster by not going over the entire array of targets in every iteration
const targets: Target[] = []
const cache: Record<number, Target> = {}
for(const track of tracks) {
if(track.target_id in cache) {
cache[track.target_id].tracks.push(track)
} else {
targets.push(cache[track.target_id] = {
id: track.target_id,
visible: true,
tracks: [track]
})
}
}
If you really want some built-in methods then .reduce would fit better:
tracks.reduce<Target[]>(
(targets, track) => {
const target = targets.find(target => target.id === track.track_id)
if(!target) {
targets.push({id: track.target_id, visible: true, tracks: [track]})
} else {
target.tracks.push(track)
}
return targets
}
)
Or if you don't like mutating objects
tracks.reduce<Target[]>(
(targets, track) => {
const index = targets.findIndex(target => target.id === track.target_id)
if(index < 0) {
return [...targets, {id: track.target_id, visible: true, tracks: [track]}]
}
return [
...targets.slice(0, index),
{...targets[index], tracks: [...targets[index].tracks, track]},
...targets.slice(index),
]
}
)
But I don't see much advantage in this code, because it is not very readable and unnecessarily copies elements between arrays so many times