UNPKG

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