zustand-computed
Version:
A Zustand middleware to create computed states.
91 lines (90 loc) • 3.08 kB
JavaScript
const isIterable = (obj) => Symbol.iterator in obj;
const hasIterableEntries = (value) => (
// HACK: avoid checking entries type
"entries" in value
);
const compareEntries = (valueA, valueB) => {
const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
if (mapA.size !== mapB.size) {
return false;
}
for (const [key, value] of mapA) {
if (!Object.is(value, mapB.get(key))) {
return false;
}
}
return true;
};
const compareIterables = (valueA, valueB) => {
const iteratorA = valueA[Symbol.iterator]();
const iteratorB = valueB[Symbol.iterator]();
let nextA = iteratorA.next();
let nextB = iteratorB.next();
while (!nextA.done && !nextB.done) {
if (!Object.is(nextA.value, nextB.value)) {
return false;
}
nextA = iteratorA.next();
nextB = iteratorB.next();
}
return !!nextA.done && !!nextB.done;
};
function shallow(valueA, valueB) {
if (Object.is(valueA, valueB)) {
return true;
}
if (typeof valueA !== "object" || valueA === null || typeof valueB !== "object" || valueB === null) {
return false;
}
if (!isIterable(valueA) || !isIterable(valueB)) {
return compareEntries(
{ entries: () => Object.entries(valueA) },
{ entries: () => Object.entries(valueB) }
);
}
if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
return compareEntries(valueA, valueB);
}
return compareIterables(valueA, valueB);
}
const computedImpl = (compute, opts) => (f) => {
const optsKeys = !opts || !("keys" in opts) || opts.keys == null ? void 0 : opts.keys;
const keysSet = optsKeys ? new Set(optsKeys) : void 0;
function defaultShouldRecomputeFn(_, nextState) {
if (!keysSet || nextState == null) return true;
return Object.keys(nextState).some((k) => keysSet.has(k));
}
const shouldRecomputeFn = opts && "shouldRecompute" in opts ? opts.shouldRecompute ?? defaultShouldRecomputeFn : defaultShouldRecomputeFn;
return (set, get, api) => {
const equalityFn = (opts == null ? void 0 : opts.equalityFn) ?? shallow;
const computeAndMerge = (state) => {
const computedState = compute({ ...state });
for (const k of Object.keys(computedState)) {
if (equalityFn(computedState[k], state[k])) {
delete computedState[k];
}
}
return { ...state, ...computedState };
};
const setWithComputed = (update, replace, ...args) => {
set(
(state) => {
const updated = typeof update === "object" ? update : update(state);
if (!(shouldRecomputeFn == null ? void 0 : shouldRecomputeFn(state, updated))) return { ...state, ...updated };
return computeAndMerge({ ...state, ...updated });
},
replace,
...args
);
};
const _api = api;
_api.setState = setWithComputed;
const st = f(setWithComputed, get, _api);
return Object.assign({}, st, compute(st));
};
};
const createComputed = computedImpl;
export {
createComputed
};