UNPKG

18.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var react = require('react');
6var useContextSelector = require('use-context-selector');
7
8function _extends() {
9 _extends = Object.assign || function (target) {
10 for (var i = 1; i < arguments.length; i++) {
11 var source = arguments[i];
12
13 for (var key in source) {
14 if (Object.prototype.hasOwnProperty.call(source, key)) {
15 target[key] = source[key];
16 }
17 }
18 }
19
20 return target;
21 };
22
23 return _extends.apply(this, arguments);
24}
25
26var isSSR = typeof window === 'undefined' || /ServerSideRendering/.test(window.navigator && window.navigator.userAgent);
27var useIsoLayoutEffect = isSSR ? function (fn) {
28 return fn();
29} : react.useLayoutEffect;
30
31var appendMap = function appendMap(dst, src) {
32 src.forEach(function (v, k) {
33 dst.set(k, v);
34 });
35 return dst;
36}; // create new map from two maps
37
38
39var concatMap = function concatMap(src1, src2) {
40 return appendMap(new Map(src1), src2);
41}; // create new map and delete item
42
43
44var deleteMapItem = function deleteMapItem(src, key) {
45 var dst = new Map(src);
46 dst.delete(key);
47 return dst;
48};
49
50var warningObject = new Proxy({}, {
51 get: function get() {
52 throw new Error('Please use <Provider>');
53 },
54 apply: function apply() {
55 throw new Error('Please use <Provider>');
56 }
57});
58
59var addDependent = function addDependent(dependentsMap, atom, dependent) {
60 var dependents = dependentsMap.get(atom);
61
62 if (!dependents) {
63 dependents = new Set();
64 dependentsMap.set(atom, dependents);
65 }
66
67 dependents.add(dependent);
68};
69
70var deleteDependent = function deleteDependent(dependentsMap, dependent) {
71 dependentsMap.forEach(function (dependents) {
72 dependents.delete(dependent);
73 });
74};
75
76var setDependencies = function setDependencies(dependentsMap, atom, dependencies) {
77 deleteDependent(dependentsMap, atom);
78 dependencies.forEach(function (dependency) {
79 addDependent(dependentsMap, dependency, atom);
80 });
81};
82
83var listDependents = function listDependents(dependentsMap, atom, excludeSelf) {
84 var dependents = new Set(dependentsMap.get(atom));
85
86 if (excludeSelf) {
87 dependents.delete(atom);
88 }
89
90 return dependents;
91};
92
93var initialState = new Map();
94
95var getAtomState = function getAtomState(atom, state, tmpState) {
96 var atomState = tmpState && tmpState.get(atom) || state.get(atom);
97
98 if (!atomState) {
99 throw new Error('atom state not found. possibly a bug.');
100 }
101
102 return atomState;
103};
104
105var getAtomStateValue = function getAtomStateValue(atom, state, tmpState) {
106 var atomState = tmpState && tmpState.get(atom) || state.get(atom);
107 return atomState ? atomState.value : atom.init;
108};
109
110var readAtom = function readAtom(state, readingAtom, setState, dependentsMap, readPendingMap) {
111 var readAtomValue = function readAtomValue(prevState, atom) {
112 var partialState = new Map();
113 var atomState = prevState.get(atom);
114
115 if (atomState) {
116 return [atomState, partialState];
117 }
118
119 var error = undefined;
120 var promise = undefined;
121 var value = null;
122 var dependencies = new Set();
123 var isSync = true;
124
125 try {
126 var promiseOrValue = atom.read(function (a) {
127 if (dependencies) {
128 dependencies.add(a);
129 } else {
130 addDependent(dependentsMap, a, atom);
131 }
132
133 if (a !== atom) {
134 var _readAtomValue = readAtomValue(concatMap(prevState, partialState), a),
135 _nextAtomState = _readAtomValue[0],
136 nextPartialState = _readAtomValue[1];
137
138 if (isSync) {
139 appendMap(partialState, nextPartialState);
140 } else {
141 setState(function (prev) {
142 return concatMap(prev, nextPartialState);
143 });
144 }
145
146 if (_nextAtomState.readE) {
147 throw _nextAtomState.readE;
148 }
149
150 if (_nextAtomState.readP) {
151 throw _nextAtomState.readP;
152 }
153
154 return _nextAtomState.value;
155 } // a === atom
156
157
158 var aState = partialState.get(a) || prevState.get(a);
159
160 if (aState) {
161 if (aState.readP) {
162 throw aState.readP;
163 }
164
165 return aState.value;
166 }
167
168 return a.init; // this should not be undefined
169 });
170
171 if (promiseOrValue instanceof Promise) {
172 promise = promiseOrValue.then(function (value) {
173 var _prev$get;
174
175 setDependencies(dependentsMap, atom, dependencies);
176 dependencies = null;
177 var prev = readPendingMap.get(atom);
178
179 if (prev && ((_prev$get = prev.get(atom)) == null ? void 0 : _prev$get.readP) === promise) {
180 readPendingMap.set(atom, deleteMapItem(prev, atom));
181 }
182
183 setState(function (prev) {
184 return new Map(prev).set(atom, {
185 value: value
186 });
187 });
188 }).catch(function (e) {
189 var _prev$get2;
190
191 var prev = readPendingMap.get(atom);
192
193 if (prev && ((_prev$get2 = prev.get(atom)) == null ? void 0 : _prev$get2.readP) === promise) {
194 readPendingMap.set(atom, deleteMapItem(prev, atom));
195 }
196
197 setState(function (prev) {
198 return new Map(prev).set(atom, {
199 value: getAtomStateValue(atom, prev),
200 readE: e instanceof Error ? e : new Error(e)
201 });
202 });
203 });
204 } else {
205 setDependencies(dependentsMap, atom, dependencies);
206 dependencies = null;
207 value = promiseOrValue;
208 }
209 } catch (errorOrPromise) {
210 if (errorOrPromise instanceof Promise) {
211 promise = errorOrPromise;
212 } else if (errorOrPromise instanceof Error) {
213 error = errorOrPromise;
214 } else {
215 error = new Error(errorOrPromise);
216 }
217 }
218
219 var nextAtomState = {
220 readE: error,
221 readP: promise,
222 value: promise ? atom.init : value
223 };
224 partialState.set(atom, nextAtomState);
225 isSync = false;
226 return [nextAtomState, partialState];
227 };
228
229 var prevPartialState = readPendingMap.get(readingAtom);
230
231 var _readAtomValue2 = readAtomValue(prevPartialState ? concatMap(state, prevPartialState) : state, readingAtom),
232 atomState = _readAtomValue2[0],
233 partialState = _readAtomValue2[1];
234
235 readPendingMap.set(readingAtom, prevPartialState ? concatMap(prevPartialState, partialState) : partialState);
236 return atomState;
237};
238
239var addAtom = function addAtom(id, atom, setState, dependentsMap, readPendingMap) {
240 addDependent(dependentsMap, atom, id);
241 var partialState = readPendingMap.get(atom);
242
243 if (partialState) {
244 readPendingMap.delete(atom);
245 setState(function (prev) {
246 return concatMap(prev, partialState);
247 });
248 }
249};
250
251var delAtom = function delAtom(id, setGcCount, dependentsMap) {
252 deleteDependent(dependentsMap, id);
253 setGcCount(function (c) {
254 return c + 1;
255 }); // trigger re-render for gc
256};
257
258var gcAtom = function gcAtom(state, setState, dependentsMap) {
259 var nextState = new Map(state);
260 var deleted;
261
262 do {
263 deleted = false;
264 nextState.forEach(function (_atomState, atom) {
265 var _dependentsMap$get;
266
267 var isEmpty = ((_dependentsMap$get = dependentsMap.get(atom)) == null ? void 0 : _dependentsMap$get.size) === 0;
268
269 if (isEmpty) {
270 nextState.delete(atom);
271 dependentsMap.delete(atom);
272 deleted = true;
273 }
274 });
275 } while (deleted);
276
277 if (nextState.size !== state.size) {
278 setState(nextState);
279 }
280};
281
282var writeAtom = function writeAtom(updatingAtom, update, setState, dependentsMap, addWriteThunk) {
283 var pendingPromises = [];
284
285 var updateDependentsState = function updateDependentsState(prevState, atom) {
286 var partialState = new Map();
287 listDependents(dependentsMap, atom, true).forEach(function (dependent) {
288 if (typeof dependent === 'symbol') return;
289 var dependencies = new Set();
290
291 try {
292 var promiseOrValue = dependent.read(function (a) {
293 if (dependencies) {
294 dependencies.add(a);
295 } else {
296 addDependent(dependentsMap, a, dependent);
297 }
298
299 var s = getAtomState(a, prevState);
300
301 if (s.readE) {
302 throw s.readE;
303 }
304
305 return s.value;
306 });
307
308 if (promiseOrValue instanceof Promise) {
309 var promise = promiseOrValue.then(function (value) {
310 setDependencies(dependentsMap, dependent, dependencies);
311 dependencies = null;
312 var nextAtomState = {
313 value: value
314 };
315 setState(function (prev) {
316 var _prev$get3;
317
318 var prevPromise = (_prev$get3 = prev.get(dependent)) == null ? void 0 : _prev$get3.readP;
319
320 if (prevPromise && prevPromise !== promise) {
321 // There is a new promise, so we skip updating this one.
322 return prev;
323 }
324
325 var nextState = new Map(prev).set(dependent, nextAtomState);
326 var nextPartialState = updateDependentsState(nextState, dependent);
327 return appendMap(nextState, nextPartialState);
328 });
329 }).catch(function (e) {
330 setState(function (prev) {
331 return new Map(prev).set(dependent, {
332 value: getAtomStateValue(dependent, prev),
333 readE: e instanceof Error ? e : new Error(e)
334 });
335 });
336 });
337 partialState.set(dependent, {
338 value: getAtomStateValue(dependent, prevState),
339 readP: promise
340 });
341 } else {
342 setDependencies(dependentsMap, dependent, dependencies);
343 dependencies = null;
344 partialState.set(dependent, {
345 value: promiseOrValue
346 });
347 appendMap(partialState, updateDependentsState(concatMap(prevState, partialState), dependent));
348 }
349 } catch (e) {
350 partialState.set(dependent, {
351 value: getAtomStateValue(dependent, prevState),
352 readE: e instanceof Error ? e : new Error(e)
353 });
354 appendMap(partialState, updateDependentsState(concatMap(prevState, partialState), dependent));
355 }
356 });
357 return partialState;
358 };
359
360 var updateAtomState = function updateAtomState(prevState, atom, update) {
361 var prevAtomState = prevState.get(atom);
362
363 if (prevAtomState && prevAtomState.writeP) {
364 var promise = prevAtomState.writeP.then(function () {
365 addWriteThunk(function (prev) {
366 var nextPartialState = updateAtomState(prev, atom, update);
367
368 if (nextPartialState) {
369 return concatMap(prevState, nextPartialState);
370 }
371
372 return prev;
373 });
374 });
375 pendingPromises.push(promise);
376 return null;
377 }
378
379 var partialState = new Map();
380 var isSync = true;
381
382 try {
383 var promiseOrVoid = atom.write(function (a) {
384 if (process.env.NODE_ENV !== 'production') {
385 var s = prevState.get(a);
386
387 if (s && s.readP) {
388 console.log('Reading pending atom state in write operation. Not sure how to deal with it. Returning stale vaule for', a);
389 }
390 }
391
392 return getAtomStateValue(a, prevState, partialState);
393 }, function (a, v) {
394 if (a === atom) {
395 var nextAtomState = {
396 value: v
397 };
398
399 if (isSync) {
400 partialState.set(a, nextAtomState);
401 appendMap(partialState, updateDependentsState(concatMap(prevState, partialState), a));
402 } else {
403 setState(function (prev) {
404 var nextState = new Map(prev).set(a, nextAtomState);
405 var nextPartialState = updateDependentsState(nextState, a);
406 return appendMap(nextState, nextPartialState);
407 });
408 }
409 } else {
410 if (isSync) {
411 var nextPartialState = updateAtomState(concatMap(prevState, partialState), a, v);
412
413 if (nextPartialState) {
414 appendMap(partialState, nextPartialState);
415 }
416 } else {
417 addWriteThunk(function (prev) {
418 var nextPartialState = updateAtomState(prev, a, v);
419
420 if (nextPartialState) {
421 return concatMap(prev, nextPartialState);
422 }
423
424 return prev;
425 });
426 }
427 }
428 }, update);
429
430 if (promiseOrVoid instanceof Promise) {
431 pendingPromises.push(promiseOrVoid);
432
433 var nextAtomState = _extends({}, getAtomState(atom, prevState, partialState), {
434 writeP: promiseOrVoid.then(function () {
435 addWriteThunk(function (prev) {
436 return new Map(prev).set(atom, _extends({}, getAtomState(atom, prev), {
437 writeP: undefined
438 }));
439 });
440 })
441 });
442
443 partialState.set(atom, nextAtomState);
444 }
445 } catch (e) {
446 if (pendingPromises.length) {
447 pendingPromises.push(new Promise(function (_resolve, reject) {
448 reject(e);
449 }));
450 } else {
451 throw e;
452 }
453 }
454
455 isSync = false;
456 return partialState;
457 };
458
459 addWriteThunk(function (prevState) {
460 var nextPartialState = updateAtomState(prevState, updatingAtom, update);
461
462 if (nextPartialState) {
463 return concatMap(prevState, nextPartialState);
464 }
465
466 return prevState;
467 });
468
469 if (pendingPromises.length) {
470 return new Promise(function (resolve, reject) {
471 var loop = function loop() {
472 var len = pendingPromises.length;
473
474 if (len === 0) {
475 resolve();
476 } else {
477 Promise.all(pendingPromises).then(function () {
478 pendingPromises.splice(0, len);
479 loop();
480 }).catch(reject);
481 }
482 };
483
484 loop();
485 });
486 }
487};
488
489var runWriteThunk = function runWriteThunk(lastStateRef, setState, writeThunkQueue) {
490 while (true) {
491 if (!lastStateRef.current) {
492 return;
493 }
494
495 if (writeThunkQueue.length === 0) {
496 return;
497 }
498
499 var thunk = writeThunkQueue.shift();
500 var lastState = lastStateRef.current;
501 var nextState = thunk(lastState);
502
503 if (nextState !== lastState) {
504 setState(nextState);
505 return;
506 }
507 }
508};
509
510var ActionsContext = useContextSelector.createContext(warningObject);
511var StateContext = useContextSelector.createContext(warningObject);
512var Provider = function Provider(_ref) {
513 var children = _ref.children;
514 var dependentsMapRef = react.useRef();
515
516 if (!dependentsMapRef.current) {
517 dependentsMapRef.current = new Map();
518 }
519
520 var readPendingMapRef = react.useRef();
521
522 if (!readPendingMapRef.current) {
523 readPendingMapRef.current = new WeakMap();
524 }
525
526 var _useState = react.useState(initialState),
527 state = _useState[0],
528 setStateOrig = _useState[1];
529
530 var lastStateRef = react.useRef(null);
531
532 var setState = function setState(setStateAction) {
533 lastStateRef.current = null;
534 setStateOrig(setStateAction);
535 };
536
537 useIsoLayoutEffect(function () {
538 if (state !== initialState) {
539 lastStateRef.current = state;
540 }
541 });
542
543 var _useState2 = react.useState(0),
544 gcCount = _useState2[0],
545 setGcCount = _useState2[1]; // to trigger gc
546
547
548 react.useEffect(function () {
549 gcAtom(state, setState, dependentsMapRef.current);
550 }, [state, gcCount]);
551 var writeThunkQueueRef = react.useRef([]);
552 react.useEffect(function () {
553 runWriteThunk(lastStateRef, setState, writeThunkQueueRef.current);
554 }, [state]);
555 var actions = react.useMemo(function () {
556 return {
557 add: function add(id, atom) {
558 return addAtom(id, atom, setState, dependentsMapRef.current, readPendingMapRef.current);
559 },
560 del: function del(id) {
561 return delAtom(id, setGcCount, dependentsMapRef.current);
562 },
563 read: function read(state, atom) {
564 return readAtom(state, atom, setState, dependentsMapRef.current, readPendingMapRef.current);
565 },
566 write: function write(atom, update) {
567 return writeAtom(atom, update, setState, dependentsMapRef.current, function (thunk) {
568 writeThunkQueueRef.current.push(thunk);
569 runWriteThunk(lastStateRef, setState, writeThunkQueueRef.current);
570 });
571 }
572 };
573 }, []);
574 return react.createElement(ActionsContext.Provider, {
575 value: actions
576 }, react.createElement(StateContext.Provider, {
577 value: state
578 }, children));
579};
580
581var keyCount = 0; // global key count for all atoms
582
583function atom(read, write) {
584 var config = {
585 key: ++keyCount
586 };
587
588 if (typeof read === 'function') {
589 config.read = read;
590 } else {
591 config.init = read;
592
593 config.read = function (get) {
594 return get(config);
595 };
596
597 config.write = function (get, set, update) {
598 set(config, typeof update === 'function' ? update(get(config)) : update);
599 };
600 }
601
602 if (write) {
603 config.write = write;
604 }
605
606 return config;
607}
608
609var isWritable = function isWritable(atom) {
610 return !!atom.write;
611};
612
613function useAtom(atom) {
614 var actions = useContextSelector.useContext(ActionsContext);
615 var value = useContextSelector.useContextSelector(StateContext, react.useCallback(function (state) {
616 var atomState = actions.read(state, atom);
617
618 if (atomState.readE) {
619 throw atomState.readE; // read error
620 }
621
622 if (atomState.readP) {
623 throw atomState.readP; // read promise
624 }
625
626 if (atomState.writeP) {
627 throw atomState.writeP; // write promise
628 }
629
630 return atomState.value;
631 }, [atom, actions]));
632 useIsoLayoutEffect(function () {
633 var id = Symbol();
634 actions.add(id, atom);
635 return function () {
636 actions.del(id);
637 };
638 }, [actions, atom]);
639 var setAtom = react.useCallback(function (update) {
640 if (isWritable(atom)) {
641 return actions.write(atom, update);
642 } else {
643 throw new Error('not writable atom');
644 }
645 }, [atom, actions]);
646 react.useDebugValue(value);
647 return [value, setAtom];
648}
649
650var useBridge = function useBridge() {
651 var actions = useContextSelector.useContext(ActionsContext);
652 var state = useContextSelector.useContext(StateContext);
653 return react.useMemo(function () {
654 return [actions, state];
655 }, [actions, state]);
656};
657var Bridge = function Bridge(_ref) {
658 var value = _ref.value,
659 children = _ref.children;
660 var actions = value[0],
661 state = value[1];
662 return react.createElement(useContextSelector.BridgeProvider, {
663 context: ActionsContext,
664 value: actions
665 }, react.createElement(useContextSelector.BridgeProvider, {
666 context: StateContext,
667 value: state
668 }, children));
669};
670
671exports.Bridge = Bridge;
672exports.Provider = Provider;
673exports.atom = atom;
674exports.useAtom = useAtom;
675exports.useBridge = useBridge;