UNPKG

jotai

Version:

👻 Primitive and flexible state management for React

152 lines (151 loc) • 6.68 kB
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); }