UNPKG

@tanstack/react-optimistic

Version:

React hooks for optimistic updates

164 lines (163 loc) 4.96 kB
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js"; import { collectionsStore, Collection } from "@tanstack/optimistic"; import { preloadCollection } from "@tanstack/optimistic"; let snapshotCache = null; function useCollections() { return useSyncExternalStoreWithSelector( (callback) => { const storeUnsubscribe = collectionsStore.subscribe(() => { snapshotCache = null; callback(); }); const collectionUnsubscribes = Array.from( collectionsStore.state.values() ).map((collection) => { const derivedStateUnsub = collection.derivedState.subscribe(() => { snapshotCache = null; callback(); }); return derivedStateUnsub; }); return () => { storeUnsubscribe(); collectionUnsubscribes.forEach((unsubscribe) => unsubscribe()); }; }, // Get snapshot of all collections and their transactions () => { if (snapshotCache) { return snapshotCache; } const snapshot = /* @__PURE__ */ new Map(); for (const [id, collection] of collectionsStore.state) { snapshot.set(id, { state: collection.derivedState.state, transactions: collection.transactions.state }); } snapshotCache = snapshot; return snapshot; }, // Server snapshot (same as client for now) () => { if (snapshotCache) { return snapshotCache; } const snapshot = /* @__PURE__ */ new Map(); for (const [id, collection] of collectionsStore.state) { snapshot.set(id, { state: collection.derivedState.state, transactions: collection.transactions.state }); } snapshotCache = snapshot; return snapshot; }, // Identity selector (state) => state, // Custom equality function for Maps (a, b) => { if (a === b) return true; if (!(a instanceof Map) || !(b instanceof Map)) return false; if (a.size !== b.size) return false; for (const [key, value] of a) { const bValue = b.get(key); if (!b.has(key)) return false; if (value instanceof Map) { if (!(bValue instanceof Map)) return false; if (!shallow(value, bValue)) return false; } else if (!Object.is(value, bValue)) { return false; } } return true; } ); } function useCollection(config, selector) { if (selector) { console.log(`selector support not yet implemented`, selector); } if (!collectionsStore.state.has(config.id)) { collectionsStore.setState((prev) => { const next = new Map(prev); next.set( config.id, new Collection({ id: config.id, sync: config.sync, schema: config.schema }) ); return next; }); } const collection = collectionsStore.state.get(config.id); const result = useSyncExternalStoreWithSelector( collection.derivedState.subscribe, () => collection.derivedState.state, () => collection.derivedState.state, (stateMap) => { return { state: stateMap, // derivedState & derivedArray are recomputed at the same time. data: collection.derivedArray.state }; }, (a, b) => { if (a === b) return true; const stateEqual = a.state.size === b.state.size && Array.from(a.state.keys()).every( (key) => shallow(a.state.get(key), b.state.get(key)) ); const dataEqual = a.data.length === b.data.length && a.data.every((datum, i) => shallow(datum, b.data[i])); return stateEqual && dataEqual; } ); const returnValue = { state: result.state, data: result.data, insert: collection.insert.bind(collection), update: collection.update.bind(collection), delete: collection.delete.bind(collection) }; return returnValue; } function shallow(objA, objB) { if (Object.is(objA, objB)) { return true; } if (typeof objA !== `object` || objA === null || typeof objB !== `object` || objB === null) { return false; } if (objA instanceof Map && objB instanceof Map) { if (objA.size !== objB.size) return false; for (const [k, v] of objA) { if (!objB.has(k) || !Object.is(v, objB.get(k))) return false; } return true; } if (objA instanceof Set && objB instanceof Set) { if (objA.size !== objB.size) return false; for (const v of objA) { if (!objB.has(v)) return false; } return true; } const keysA = Object.keys(objA); if (keysA.length !== Object.keys(objB).length) { return false; } for (const key of keysA) { if (!Object.prototype.hasOwnProperty.call(objB, key) || !Object.is(objA[key], objB[key])) { return false; } } return true; } export { preloadCollection, shallow, useCollection, useCollections }; //# sourceMappingURL=useCollection.js.map