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
JavaScript
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",
]);