1 | import { useLayoutEffect, useState, useRef, useEffect, useMemo, createElement, useCallback, useDebugValue } from 'react';
|
2 | import { createContext, useContext, useContextSelector } 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) => {
|
34 | const dst = new Map();
|
35 | src1.forEach((v, k) => {
|
36 | dst.set(k, v);
|
37 | });
|
38 | src2.forEach((v, k) => {
|
39 | dst.set(k, v);
|
40 | });
|
41 | return dst;
|
42 | };
|
43 |
|
44 | const warningObject = new Proxy({}, {
|
45 | get() {
|
46 | throw new Error('Please use <Provider>');
|
47 | },
|
48 |
|
49 | apply() {
|
50 | throw new Error('Please use <Provider>');
|
51 | }
|
52 |
|
53 | });
|
54 |
|
55 | const addDependent = (dependentsMap, atom, dependent) => {
|
56 | let dependents = dependentsMap.get(atom);
|
57 |
|
58 | if (!dependents) {
|
59 | dependents = new Set();
|
60 | dependentsMap.set(atom, dependents);
|
61 | }
|
62 |
|
63 | dependents.add(dependent);
|
64 | };
|
65 |
|
66 | const deleteDependent = (dependentsMap, atom, dependent) => {
|
67 | const dependents = dependentsMap.get(atom);
|
68 |
|
69 | if (dependents && dependents.has(dependent)) {
|
70 | dependents.delete(dependent);
|
71 | return dependents.size === 0;
|
72 | }
|
73 |
|
74 | return false;
|
75 | };
|
76 |
|
77 | const listDependents = (dependentsMap, atom) => {
|
78 | const dependents = dependentsMap.get(atom);
|
79 | return dependents || new Set();
|
80 | };
|
81 |
|
82 | const initialState = new Map();
|
83 |
|
84 | const getAtomStateValue = (state, atom) => {
|
85 | const atomState = state.get(atom);
|
86 | return atomState ? atomState.value : atom.init;
|
87 | };
|
88 |
|
89 | const readAtom = (state, readingAtom, setState, dependentsMap) => {
|
90 | const readAtomValue = (prevState, atom, dependent) => {
|
91 | if (dependent) {
|
92 | addDependent(dependentsMap, atom, dependent);
|
93 | }
|
94 |
|
95 | const partialState = new Map();
|
96 | const atomState = prevState.get(atom);
|
97 |
|
98 | if (atomState) {
|
99 | return [atomState, partialState];
|
100 | }
|
101 |
|
102 | let error = undefined;
|
103 | let promise = undefined;
|
104 | let value = null;
|
105 | let isSync = true;
|
106 |
|
107 | try {
|
108 | const promiseOrValue = atom.read(a => {
|
109 | if (a !== atom) {
|
110 | const [nextAtomState, nextPartialState] = readAtomValue(prevState, a, atom);
|
111 |
|
112 | if (isSync) {
|
113 | appendMap(partialState, nextPartialState);
|
114 | } else {
|
115 | setState(prev => appendMap(new Map(prev), nextPartialState));
|
116 | }
|
117 |
|
118 | if (nextAtomState.error) {
|
119 | throw nextAtomState.error;
|
120 | }
|
121 |
|
122 | if (nextAtomState.promise) {
|
123 | throw nextAtomState.promise;
|
124 | }
|
125 |
|
126 | return nextAtomState.value;
|
127 | }
|
128 |
|
129 |
|
130 | const aState = prevState.get(a);
|
131 |
|
132 | if (aState) {
|
133 | if (aState.promise) {
|
134 | throw aState.promise;
|
135 | }
|
136 |
|
137 | return aState.value;
|
138 | }
|
139 |
|
140 | return a.init;
|
141 | });
|
142 |
|
143 | if (promiseOrValue instanceof Promise) {
|
144 | promise = promiseOrValue.then(value => {
|
145 | setState(prev => new Map(prev).set(atom, {
|
146 | value
|
147 | }));
|
148 | }).catch(e => {
|
149 | setState(prev => new Map(prev).set(atom, {
|
150 | value: getAtomStateValue(prev, atom),
|
151 | error: e instanceof Error ? e : new Error(e)
|
152 | }));
|
153 | });
|
154 | } else {
|
155 | value = promiseOrValue;
|
156 | }
|
157 | } catch (errorOrPromise) {
|
158 | if (errorOrPromise instanceof Promise) {
|
159 | promise = errorOrPromise;
|
160 | } else if (errorOrPromise instanceof Error) {
|
161 | error = errorOrPromise;
|
162 | } else {
|
163 | error = new Error(errorOrPromise);
|
164 | }
|
165 | }
|
166 |
|
167 | const nextAtomState = {
|
168 | error,
|
169 | promise,
|
170 | value: promise ? atom.init : value
|
171 | };
|
172 | partialState.set(atom, nextAtomState);
|
173 | isSync = false;
|
174 | return [nextAtomState, partialState];
|
175 | };
|
176 |
|
177 | return readAtomValue(state, readingAtom, null);
|
178 | };
|
179 |
|
180 | const addAtom = (id, atom, partialState, setState, dependentsMap) => {
|
181 | addDependent(dependentsMap, atom, id);
|
182 |
|
183 | if (partialState) {
|
184 | setState(prev => appendMap(new Map(prev), partialState));
|
185 | }
|
186 | };
|
187 |
|
188 | const delAtom = (id, setState, dependentsMap, gcRequiredRef) => {
|
189 | const deleteAtomState = (prevState, dependent) => {
|
190 | prevState.forEach((_atomState, atom) => {
|
191 | deleteDependent(dependentsMap, atom, dependent);
|
192 | });
|
193 | return new Map(prevState);
|
194 | };
|
195 |
|
196 | gcRequiredRef.current = true;
|
197 | setState(prev => deleteAtomState(prev, id));
|
198 | };
|
199 |
|
200 | const gcAtom = (state, setState, dependentsMap) => {
|
201 | const gcAtomState = prevState => {
|
202 | const nextState = new Map(prevState);
|
203 |
|
204 | while (true) {
|
205 | let deleted = false;
|
206 | nextState.forEach((_atomState, atom) => {
|
207 | var _dependentsMap$get;
|
208 |
|
209 | const isEmpty = ((_dependentsMap$get = dependentsMap.get(atom)) == null ? void 0 : _dependentsMap$get.size) === 0;
|
210 |
|
211 | if (isEmpty) {
|
212 | nextState.delete(atom);
|
213 | dependentsMap.delete(atom);
|
214 | deleted = true;
|
215 | }
|
216 | });
|
217 |
|
218 | if (!deleted) {
|
219 | break;
|
220 | }
|
221 | }
|
222 |
|
223 | return nextState;
|
224 | };
|
225 |
|
226 | const nextState = gcAtomState(state);
|
227 |
|
228 | if (state.size !== nextState.size) {
|
229 | setState(nextState);
|
230 | }
|
231 | };
|
232 |
|
233 | const writeAtom = (updatingAtom, update, dependentsMap, addWriteThunk) => {
|
234 | const updateDependentsState = (prevState, atom) => {
|
235 | const partialState = new Map();
|
236 | listDependents(dependentsMap, atom).forEach(dependent => {
|
237 | if (typeof dependent === 'symbol') return;
|
238 | const v = dependent.read(a => {
|
239 | if (a !== dependent) {
|
240 | addDependent(dependentsMap, a, dependent);
|
241 | }
|
242 |
|
243 | return getAtomStateValue(prevState, a);
|
244 | });
|
245 |
|
246 | if (v instanceof Promise) {
|
247 | const promise = v.then(vv => {
|
248 | const nextAtomState = {
|
249 | value: vv
|
250 | };
|
251 | addWriteThunk(prev => {
|
252 | const nextState = new Map(prev).set(dependent, nextAtomState);
|
253 | const nextPartialState = updateDependentsState(nextState, dependent);
|
254 | return appendMap(nextState, nextPartialState);
|
255 | });
|
256 | }).catch(e => {
|
257 | addWriteThunk(prev => new Map(prev).set(dependent, {
|
258 | value: getAtomStateValue(prev, dependent),
|
259 | error: e instanceof Error ? e : new Error(e)
|
260 | }));
|
261 | });
|
262 | partialState.set(dependent, {
|
263 | value: getAtomStateValue(prevState, dependent),
|
264 | promise
|
265 | });
|
266 | } else {
|
267 | partialState.set(dependent, {
|
268 | value: v
|
269 | });
|
270 | appendMap(partialState, updateDependentsState(concatMap(prevState, partialState), dependent));
|
271 | }
|
272 | });
|
273 | return partialState;
|
274 | };
|
275 |
|
276 | const updateAtomState = (prevState, atom, update) => {
|
277 | const partialState = new Map();
|
278 | let isSync = true;
|
279 |
|
280 | try {
|
281 | const promise = atom.write(a => getAtomStateValue(concatMap(prevState, partialState), a), (a, v) => {
|
282 | if (a === atom) {
|
283 | const nextAtomState = {
|
284 | value: v
|
285 | };
|
286 |
|
287 | if (isSync) {
|
288 | partialState.set(a, nextAtomState);
|
289 | appendMap(partialState, updateDependentsState(concatMap(prevState, partialState), a));
|
290 | } else {
|
291 | addWriteThunk(prev => {
|
292 | const nextState = new Map(prev).set(a, nextAtomState);
|
293 | const nextPartialState = updateDependentsState(nextState, a);
|
294 | return appendMap(nextState, nextPartialState);
|
295 | });
|
296 | }
|
297 | } else {
|
298 | if (isSync) {
|
299 | const nextPartialState = updateAtomState(prevState, a, v);
|
300 | appendMap(partialState, nextPartialState);
|
301 | } else {
|
302 | addWriteThunk(prev => {
|
303 | const nextPartialState = updateAtomState(prev, a, v);
|
304 | return appendMap(new Map(prev), nextPartialState);
|
305 | });
|
306 | }
|
307 | }
|
308 | }, update);
|
309 |
|
310 | if (promise instanceof Promise) {
|
311 | const nextAtomState = {
|
312 | value: getAtomStateValue(concatMap(prevState, partialState), atom),
|
313 | promise: promise.then(() => {
|
314 | addWriteThunk(prev => new Map(prev).set(atom, {
|
315 | value: getAtomStateValue(prev, atom),
|
316 | promise: undefined
|
317 | }));
|
318 | }).catch(e => {
|
319 | addWriteThunk(prev => new Map(prev).set(atom, {
|
320 | value: getAtomStateValue(prev, atom),
|
321 | error: e instanceof Error ? e : new Error(e)
|
322 | }));
|
323 | })
|
324 | };
|
325 | partialState.set(atom, nextAtomState);
|
326 | }
|
327 | } catch (e) {
|
328 | const nextAtomState = {
|
329 | value: getAtomStateValue(concatMap(prevState, partialState), atom),
|
330 | error: e instanceof Error ? e : new Error(e)
|
331 | };
|
332 | partialState.set(atom, nextAtomState);
|
333 | }
|
334 |
|
335 | isSync = false;
|
336 | return partialState;
|
337 | };
|
338 |
|
339 | addWriteThunk(prevState => {
|
340 | const updatingAtomState = prevState.get(updatingAtom);
|
341 |
|
342 | if (updatingAtomState && updatingAtomState.promise) {
|
343 |
|
344 | const promise = updatingAtomState.promise.then(() => {
|
345 | const updateState = updateAtomState(prevState, updatingAtom, update);
|
346 | addWriteThunk(prev => appendMap(new Map(prev), updateState));
|
347 | });
|
348 | return new Map(prevState).set(updatingAtom, _extends({}, updatingAtomState, {
|
349 | promise
|
350 | }));
|
351 | } else {
|
352 | const updateState = updateAtomState(prevState, updatingAtom, update);
|
353 | return appendMap(new Map(prevState), updateState);
|
354 | }
|
355 | });
|
356 | };
|
357 |
|
358 | const runWriteThunk = (lastStateRef, setState, writeThunkQueue) => {
|
359 | while (true) {
|
360 | if (lastStateRef.current === null) {
|
361 | return;
|
362 | }
|
363 |
|
364 | if (writeThunkQueue.length === 0) {
|
365 | return;
|
366 | }
|
367 |
|
368 | const thunk = writeThunkQueue.shift();
|
369 | const lastState = lastStateRef.current;
|
370 | const nextState = thunk(lastState);
|
371 |
|
372 | if (nextState !== lastState) {
|
373 | setState(nextState);
|
374 | return;
|
375 | }
|
376 | }
|
377 | };
|
378 |
|
379 | const ActionsContext = createContext(warningObject);
|
380 | const StateContext = createContext(warningObject);
|
381 | const Provider = ({
|
382 | children
|
383 | }) => {
|
384 | const [state, setStateOrig] = useState(initialState);
|
385 |
|
386 | const setState = setStateAction => {
|
387 | lastStateRef.current = null;
|
388 | setStateOrig(setStateAction);
|
389 | };
|
390 |
|
391 | const dependentsMapRef = useRef();
|
392 |
|
393 | if (!dependentsMapRef.current) {
|
394 | dependentsMapRef.current = new WeakMap();
|
395 | }
|
396 |
|
397 | const gcRequiredRef = useRef(false);
|
398 | useEffect(() => {
|
399 | if (!gcRequiredRef.current) {
|
400 | return;
|
401 | }
|
402 |
|
403 | gcAtom(state, setState, dependentsMapRef.current);
|
404 | gcRequiredRef.current = false;
|
405 | }, [state]);
|
406 | const lastStateRef = useRef(null);
|
407 | useIsoLayoutEffect(() => {
|
408 | lastStateRef.current = state;
|
409 | });
|
410 | const writeThunkQueueRef = useRef([]);
|
411 | useEffect(() => {
|
412 | runWriteThunk(lastStateRef, setState, writeThunkQueueRef.current);
|
413 | }, [state]);
|
414 | const actions = useMemo(() => ({
|
415 | add: (id, atom, partialState) => addAtom(id, atom, partialState, setState, dependentsMapRef.current),
|
416 | del: id => delAtom(id, setState, dependentsMapRef.current, gcRequiredRef),
|
417 | read: (state, atom) => readAtom(state, atom, setState, dependentsMapRef.current),
|
418 | write: (atom, update) => {
|
419 | writeAtom(atom, update, dependentsMapRef.current, thunk => {
|
420 | writeThunkQueueRef.current.push(thunk);
|
421 | runWriteThunk(lastStateRef, setState, writeThunkQueueRef.current);
|
422 | });
|
423 | }
|
424 | }), []);
|
425 | return createElement(ActionsContext.Provider, {
|
426 | value: actions
|
427 | }, createElement(StateContext.Provider, {
|
428 | value: state
|
429 | }, children));
|
430 | };
|
431 |
|
432 | let keyCount = 0;
|
433 |
|
434 | function atom(read, write) {
|
435 | const config = {
|
436 | key: ++keyCount
|
437 | };
|
438 |
|
439 | if (typeof read === 'function') {
|
440 | config.read = read;
|
441 | } else {
|
442 | config.init = read;
|
443 |
|
444 | config.read = get => get(config);
|
445 |
|
446 | config.write = (get, set, update) => {
|
447 | set(config, typeof update === 'function' ? update(get(config)) : update);
|
448 | };
|
449 | }
|
450 |
|
451 | if (write) {
|
452 | config.write = write;
|
453 | }
|
454 |
|
455 | return config;
|
456 | }
|
457 |
|
458 | const isWritable = atom => !!atom.write;
|
459 |
|
460 | function useAtom(atom) {
|
461 | const actions = useContext(ActionsContext);
|
462 | const pendingListRef = useRef([]);
|
463 | const value = useContextSelector(StateContext, useCallback(state => {
|
464 | let atomState = state.get(atom);
|
465 |
|
466 | if (!atomState) {
|
467 | const [initialAtomState, pendingPartialState] = actions.read(state, atom);
|
468 | atomState = initialAtomState;
|
469 |
|
470 | if (!atomState.error && !atomState.promise && pendingPartialState.size) {
|
471 | pendingListRef.current.unshift({
|
472 | v: initialAtomState.value,
|
473 | p: pendingPartialState
|
474 | });
|
475 | }
|
476 | }
|
477 |
|
478 | if (atomState.error) {
|
479 | throw atomState.error;
|
480 | }
|
481 |
|
482 | if (atomState.promise) {
|
483 | throw atomState.promise;
|
484 | }
|
485 |
|
486 | return atomState.value;
|
487 | }, [atom, actions]));
|
488 | const pendingPartialStateRef = useRef();
|
489 | useIsoLayoutEffect(() => {
|
490 | const pendingList = pendingListRef.current;
|
491 | const found = pendingList.find(({
|
492 | v
|
493 | }) => v === value);
|
494 |
|
495 | if (found) {
|
496 | pendingPartialStateRef.current = found.p;
|
497 | }
|
498 |
|
499 | pendingList.splice(0, pendingList.length);
|
500 | });
|
501 | useIsoLayoutEffect(() => {
|
502 | const id = Symbol();
|
503 | actions.add(id, atom, pendingPartialStateRef.current);
|
504 | return () => {
|
505 | actions.del(id);
|
506 | };
|
507 | }, [actions, atom]);
|
508 | const setAtom = useCallback(update => {
|
509 | if (isWritable(atom)) {
|
510 | actions.write(atom, update);
|
511 | } else {
|
512 | throw new Error('not writable atom');
|
513 | }
|
514 | }, [atom, actions]);
|
515 | useDebugValue(value);
|
516 | return [value, setAtom];
|
517 | }
|
518 |
|
519 | export { Provider, atom, useAtom };
|