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