UNPKG

use-s-react

Version:

useS: the definitive React hook for local and global state — effortless, zero boilerplate, one line, superpowers included

239 lines (238 loc) 7.44 kB
import { TypeCheck } from "full-copy"; export function isValidChange(prev, next) { if (!isSupported(next)) return false; if (!isSameType(prev, next)) { if (isNullable(prev, next)) return true; return false; } else return areEqual(prev, next) === "different"; } function isSupported(next) { return supportedTypes.has(TypeCheck(next)[0]); } function isNullable(prev, next) { const typePrev = TypeCheck(prev)[0]; const typeNext = TypeCheck(next)[0]; if ((typePrev === "null" && typeNext !== "null") || (typePrev === "undefined" && typeNext !== "undefined") || (typeNext === "null" && typePrev !== "null") || (typeNext === "undefined" && typePrev !== "undefined")) return true; return false; } function isSameType(prev, next) { return TypeCheck(prev)[0] === TypeCheck(next)[0]; } function areEqual(prev, next) { if (Object.is(prev, next)) return "equals"; switch (TypeCheck(prev)[0]) { case "date": return prev.getTime() === next.getTime() ? "equals" : "different"; case "regexp": return prev.source === next.source && prev.flags === next.flags ? "equals" : "different"; case "array": return isSameArray(prev, next); case "set": return isSameSet(prev, next); case "map": return isSameMap(prev, next); case "object": return hasMeaningfulChange(prev, next); default: return "different"; } } function isSameArray(prev, next) { if (prev.length === 0 && next.length === 0) return "equals"; if (next.length === 0) return "different"; if (!next.every((item) => isSupported(item))) return "incompatible"; if (prev.length !== next.length) return "different"; for (let i = 0; i < prev.length; i++) { if (!isSameType(prev[i], next[i])) return "different"; let res; if (TypeCheck(prev[i])[0] === "object") { res = hasMeaningfulChange(prev[i], next[i], true); } else res = areEqual(prev[i], next[i]); if (res === "incompatible") return "incompatible"; if (res === "different") return "different"; } return "equals"; } function isSameSet(prev, next) { if (prev.size === 0 && next.size === 0) return "equals"; if (next.size === 0) return "different"; for (const item of next) { if (!isSupported(item)) return "incompatible"; } if (prev.size !== next.size) return "different"; for (const prevItem of prev) { let found = false; for (const nextItem of next) { if (!isSameType(prevItem, nextItem)) continue; let res; if (TypeCheck(prevItem)[0] === "object") { res = hasMeaningfulChange(prevItem, nextItem, true); } else res = areEqual(prevItem, nextItem); if (res === "incompatible") return "incompatible"; if (res === "equals") { found = true; break; } } if (!found) return "different"; } return "equals"; } function isSameMap(prev, next) { if (prev.size === 0 && next.size === 0) return "equals"; if (next.size === 0) return "different"; for (const [key, val] of next) { if (!isSupported(key)) return "incompatible"; if (!isSupported(val)) return "incompatible"; } if (prev.size !== next.size) return "different"; for (const [keyPrev, valPrev] of prev) { let found = false; for (const [keyNext, valNext] of next) { if (!isSameType(keyPrev, keyNext)) continue; let keyRes; if (TypeCheck(keyPrev)[0] === "object") { keyRes = hasMeaningfulChange(keyPrev, keyNext, true); } else keyRes = areEqual(keyPrev, keyNext); if (keyRes === "incompatible") return "incompatible"; if (keyRes === "equals") { if (!isSameType(valPrev, valNext)) continue; let valRes; if (TypeCheck(valPrev)[0] === "object") { valRes = hasMeaningfulChange(valPrev, valNext, true); } else valRes = areEqual(valPrev, valNext); if (valRes === "incompatible") return "incompatible"; found = true; break; } } if (!found) return "different"; } return "equals"; } function hasMeaningfulChange(prev, next, fullCompare) { if (fullCompare && Object.is(prev, next)) return "equals"; const prevKeys = Object.keys(prev); const nextKeys = Object.keys(next); if (prevKeys.length === 0 && nextKeys.length === 0) return "equals"; if (nextKeys.length === 0) return "different"; if (prevKeys.length === 0) return isNextSupported(next); if (fullCompare && prevKeys.length !== nextKeys.length) return isNextSupported(next); const commonKeys = prevKeys.filter((key) => key in next); if (!fullCompare && commonKeys.length === 0) return "incompatible"; if (fullCompare && commonKeys.length === 0) return "different"; const arr = []; for (const key of commonKeys) { const prevValue = prev[key]; const nextValue = next[key]; if (!isSupported(nextValue)) return "incompatible"; let res; if (fullCompare) { if (!isSameType(prevValue, nextValue)) res = "different"; else if (TypeCheck(prevValue)[0] === "object") { res = hasMeaningfulChange(prevValue, nextValue, true); } else res = areEqual(prevValue, nextValue); } else { if (!isSameType(prevValue, nextValue) && !isNullable(prevValue, nextValue)) return "incompatible"; else if (isNullable(prevValue, nextValue)) res = "different"; else res = areEqual(prevValue, nextValue); } if (res === "incompatible") return "incompatible"; arr.push(res); } return arr.every((item) => item === "equals") ? "equals" : "different"; } function isNextSupported(next) { return hasOnlySupportedValues(next) ? "different" : "incompatible"; } function hasOnlySupportedValues(obj) { const values = Object.values(obj); for (let i = 0; i < values.length; i++) { if (!isSupported(values[i])) return false; if (TypeCheck(values[i])[0] === "object") { if (!hasOnlySupportedValues(values[i])) { return false; } } } return true; } const supportedTypes = new Set([ "number", "string", "boolean", "bigint", "null", "undefined", "date", "regexp", "set", "map", "array", "object", "function", ]);