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