jotai
Version:
👻 Primitive and flexible state management for React
152 lines (151 loc) • 6.68 kB
JavaScript
import { atom } from '../../vanilla.js';
const getCached = (c, m, k) => (m.has(k) ? m : m.set(k, c())).get(k);
const cache1 = new WeakMap();
const memo2 = (create, dep1, dep2) => {
const cache2 = getCached(() => new WeakMap(), cache1, dep1);
return getCached(create, cache2, dep2);
};
const cacheKeyForEmptyKeyExtractor = {};
const isWritable = (atom) => !!atom.write;
const isFunction = (x) => typeof x === 'function';
export function splitAtom(arrAtom, keyExtractor) {
return memo2(() => {
const mappingCache = new WeakMap();
const getMapping = (arr, prev) => {
let mapping = mappingCache.get(arr);
if (mapping) {
return mapping;
}
const prevMapping = prev && mappingCache.get(prev);
const atomList = [];
const keyList = [];
arr.forEach((item, index) => {
const key = keyExtractor
? keyExtractor(item)
: index;
keyList[index] = key;
const cachedAtom = prevMapping &&
prevMapping.atomList[prevMapping.keyList.indexOf(key)];
if (cachedAtom) {
atomList[index] = cachedAtom;
return;
}
const read = (get) => {
const prev = get(mappingAtom);
const currArr = get(arrAtom);
const mapping = getMapping(currArr, prev?.arr);
const index = mapping.keyList.indexOf(key);
if (index < 0 || index >= currArr.length) {
// returning a stale value to avoid errors for use cases such as react-spring
const prevItem = arr[getMapping(arr).keyList.indexOf(key)];
if (prevItem) {
return prevItem;
}
throw new Error('splitAtom: index out of bounds for read');
}
return currArr[index];
};
const write = (get, set, update) => {
const prev = get(mappingAtom);
const arr = get(arrAtom);
const mapping = getMapping(arr, prev?.arr);
const index = mapping.keyList.indexOf(key);
if (index < 0 || index >= arr.length) {
throw new Error('splitAtom: index out of bounds for write');
}
const nextItem = isFunction(update)
? update(arr[index])
: update;
if (!Object.is(arr[index], nextItem)) {
set(arrAtom, [
...arr.slice(0, index),
nextItem,
...arr.slice(index + 1),
]);
}
};
atomList[index] = isWritable(arrAtom) ? atom(read, write) : atom(read);
});
if (prevMapping &&
prevMapping.keyList.length === keyList.length &&
prevMapping.keyList.every((x, i) => x === keyList[i])) {
// not changed
mapping = prevMapping;
}
else {
mapping = { arr, atomList, keyList };
}
mappingCache.set(arr, mapping);
return mapping;
};
const mappingAtom = atom((get) => {
const prev = get(mappingAtom);
const arr = get(arrAtom);
const mapping = getMapping(arr, prev?.arr);
return mapping;
});
if (process.env.NODE_ENV !== 'production') {
mappingAtom.debugPrivate = true;
}
// HACK to read mapping atom before initialization
mappingAtom.init = undefined;
const splittedAtom = isWritable(arrAtom)
? atom((get) => get(mappingAtom).atomList, (get, set, action) => {
switch (action.type) {
case 'remove': {
const index = get(splittedAtom).indexOf(action.atom);
if (index >= 0) {
const arr = get(arrAtom);
set(arrAtom, [
...arr.slice(0, index),
...arr.slice(index + 1),
]);
}
break;
}
case 'insert': {
const index = action.before
? get(splittedAtom).indexOf(action.before)
: get(splittedAtom).length;
if (index >= 0) {
const arr = get(arrAtom);
set(arrAtom, [
...arr.slice(0, index),
action.value,
...arr.slice(index),
]);
}
break;
}
case 'move': {
const index1 = get(splittedAtom).indexOf(action.atom);
const index2 = action.before
? get(splittedAtom).indexOf(action.before)
: get(splittedAtom).length;
if (index1 >= 0 && index2 >= 0) {
const arr = get(arrAtom);
if (index1 < index2) {
set(arrAtom, [
...arr.slice(0, index1),
...arr.slice(index1 + 1, index2),
arr[index1],
...arr.slice(index2),
]);
}
else {
set(arrAtom, [
...arr.slice(0, index2),
arr[index1],
...arr.slice(index2, index1),
...arr.slice(index1 + 1),
]);
}
}
break;
}
}
})
: atom((get) => get(mappingAtom).atomList); // read-only atom
return splittedAtom;
}, arrAtom, keyExtractor || cacheKeyForEmptyKeyExtractor);
}