Skip to content

Instantly share code, notes, and snippets.

@szhu
Created April 30, 2024 06:03
Show Gist options
  • Save szhu/c4769cf349a3a11ffefef141b4801be5 to your computer and use it in GitHub Desktop.
Save szhu/c4769cf349a3a11ffefef141b4801be5 to your computer and use it in GitHub Desktop.
type ItemInfo =
| {
type: "element";
key: string | undefined;
}
| { type: "other" };
function getItemInfo(item: unknown): ItemInfo {
if (typeof item === "object" && item != null) {
if ("key" in item) {
return {
type: "element",
key: item.key == null ? undefined : "" + item.key,
};
}
}
return { type: "other" };
}
function reprItem(index: number, info: ItemInfo) {
const reprIndex = `[${index}]:`;
switch (info.type) {
case "element":
if (info.key == null) {
return `${reprIndex} <... />`;
} else {
return `${reprIndex} <... key=${JSON.stringify(info.key)} />`;
}
case "other":
return "${reprIndex} (not a React element)";
}
}
function reprTrace(
items: unknown[],
messageIndex: number,
message: string,
): string {
const lines = items.map((item, index) => {
const info = getItemInfo(item);
const itemAsString = reprItem(index, info);
if (index === messageIndex) {
return `${itemAsString} <- ❌ ${message}`;
} else {
return itemAsString;
}
});
return lines.join("\n");
}
/**
* React's warnings about missing and duplicated keys is frustratingly vague and
* doesn't do much to help you pinpoint the specific place where the issue is
* happening. This function helps in two ways:
* - Unlike React's warning, this function runs when the array is created, so
* looking at backtrace for the function call will tell you exactly where the
* faulty array is created.
* - Even so, sometimes it's still hard to figure out why the issue is
* happening. This function will print out the keys of all the items in each
* problematic array, making it easier to debug why the issue is happening.
* - Many React devs are not aware that React keys are coerced to strings and
* that a key of `undefined` is treated as a missing key. This function will
* make that more obvious.
*
* This function only runs checks in development mode, so there's no risk to
* performance or runtime behavior in production.
*
* Here's an example of what the warning might look like:
*
* Issue(s) detected with React keys for this array:
* [0]: <... key="false" />
* [1]: <... /> <- ❌ missing key
* [2]: <... key="hello" />
* [3]: <... key="test" />
* [4]: <... key="[123]" />
* [5]: <... key="null" />
* [6]: <... key="///" />
*
*/
export default function checkReactKeys<T extends unknown[]>(items: T): T {
if (process.env.NODE_ENV === "development") {
const seenKeys = new Set<string>();
items.forEach((item, index) => {
const info = getItemInfo(item);
if (info.type !== "element") return;
if (info.key == null) {
const trace = reprTrace(items, index, "missing key");
console.warn(
`Issue(s) detected with React keys for this array:\n${trace}`,
);
} else if (seenKeys.has(info.key)) {
const trace = reprTrace(items, index, "duplicate key");
console.warn(
`Issue(s) detected with React keys for this array:\n${trace}`,
);
} else {
seenKeys.add(info.key);
}
});
}
return items;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment