|
/* eslint-disable @typescript-eslint/no-explicit-any */ |
|
import { useState } from "react"; |
|
|
|
interface Change { |
|
key: string; |
|
hasChanged: boolean; |
|
hasJsonChanges: boolean; |
|
deepChanges: Change[]; |
|
appeared: boolean; |
|
disappeared: boolean; |
|
} |
|
|
|
function getChanges(current: any, last: any): Change[] { |
|
return Object.entries(current).map(([key, value]) => { |
|
const hasChanged = value !== last[key]; |
|
const hasJsonChanges = JSON.stringify(value) !== JSON.stringify(last[key]); |
|
let deepChanges: Change[] = []; |
|
if ( |
|
value && |
|
last[key] && |
|
typeof value === "object" && |
|
typeof last[key] === "object" |
|
) { |
|
deepChanges = getChanges(value, last[key]).filter((c) => c.hasChanged); |
|
} |
|
|
|
let appeared = false; |
|
let disappeared = false; |
|
if ( |
|
(typeof value !== "boolean" || typeof last[key] !== "boolean") && |
|
!!value !== !!last[key] |
|
) { |
|
if (value) { |
|
appeared = true; |
|
} |
|
if (last[key]) { |
|
disappeared = true; |
|
} |
|
} |
|
|
|
return { |
|
key, |
|
hasChanged, |
|
hasJsonChanges, |
|
deepChanges, |
|
appeared, |
|
disappeared, |
|
}; |
|
}); |
|
} |
|
|
|
function formatChangeShort(change: Change): string[] { |
|
const deepChanges = change.deepChanges.flatMap(formatChangeShort); |
|
if (deepChanges.length > 0) { |
|
if (deepChanges.length < 10) { |
|
return deepChanges.map((key) => `${change.key}.${key}`); |
|
} else { |
|
return [`${change.key}.*`]; |
|
} |
|
} |
|
|
|
return [change.key]; |
|
} |
|
|
|
function formatChangeList(change: Change): string { |
|
let infos = change.hasChanged ? "CHANGED" : "unchanged"; |
|
if (change.hasChanged && !change.hasJsonChanges) { |
|
infos += " (same json)"; |
|
} |
|
if (change.deepChanges.length) { |
|
infos += ` (deep changes: ${change.deepChanges |
|
.flatMap(formatChangeShort) |
|
.join(", ")})`; |
|
} |
|
|
|
if (change.appeared) { |
|
infos += ` (appeared)`; |
|
} |
|
if (change.disappeared) { |
|
infos += ` (disappeared)`; |
|
} |
|
|
|
return `- '${change.key}' ${infos}`; |
|
} |
|
|
|
const lastState: Record<string, any> = {}; |
|
|
|
/** |
|
* Log changes in an object, e.g. the props |
|
* |
|
* Can be used to debug re-rendering and React.memo |
|
*/ |
|
export default function useDebugChanges(what: string, current: any) { |
|
const [debugId] = useState(what + "_" + Math.random().toString()); |
|
|
|
const last = lastState[debugId] || {}; |
|
|
|
const changes = getChanges(current, last); |
|
console.log(`${what}:\n` + changes.map(formatChangeList).join("\n")); |
|
|
|
lastState[debugId] = current; |
|
} |