When iterating through an array in React, a key
attribute is expected on the rendered elements, so a reordering is properly rendered. However, often the objects we’re rendering have no unique ID and using the plain index will give us a broken rendering when a reorder happens. So what should we use?
My first idea was to use the object itself as the key
, but it must be a string or a number. Then, while researching the matter, I found a rather good solution: using a WeakMap
object. I wasn’t even aware that such WeakMap
existed, and turns out it’s perfect for the job.
A WeakMap
is basically a Map
which uses objects as keys. The difference from an ordinary Map
is that the Map
would retain the objects indefinitely – they would simply pile up, what can be seen as a memory leak –, while the WeakMap
lets the objects being garbage collected when they are no longer referenced anywhere.
Given that concept imagine the following interface:
interface Person {
name: string;
age: number;
}
Now we have a React component which needs to render an array of Person
. This is how we can write it:
interface Props {
people: Person[];
}
function ThePeople(props: Props) {
return <>
{props.people.map(person =>
<div key={getId(person)}>
{person.name}, {person.age}
</div>
)}
</>;
}
Note the getId
function in the code above, which somewhat returns an unique ID for the object.
We’ll use a WeakMap
to store the Person
objects along with an auto-generated number
, which will be its unique ID::
let currentId = 0;
let ids = new WeakMap<Object, number>();
export function getId(obj: Object): number {
if (ids.has(obj)) {
return ids.get(obj)!;
} else {
const newId = ++currentId;
ids.set(obj, newId);
return newId;
}
}
For each object, the ID is set once, and it can be retrieved any number of times. This effectively eliminates the need of an alien _id
field in our struct, and it also prevents the memory leaking of using an ordinary Map
.
However, when using immutable state – which is basically the norm in React –, you’ll always have different objects, thus different IDs, and this will cause the loss of focus on elements. So, despite its ugliness, an _id
attribute is still better. Or, if the list element won’t reorder, a simple index.