Created
April 30, 2024 06:03
-
-
Save szhu/c4769cf349a3a11ffefef141b4801be5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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