@tomorrowevening/theatre-react 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +119 -128
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +197 -0
- package/dist/index.js.map +7 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/package.json +7 -10
package/dist/index.d.ts
CHANGED
|
@@ -1,128 +1,119 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React bindings for dataverse.
|
|
3
|
-
*
|
|
4
|
-
* @packageDocumentation
|
|
5
|
-
*/
|
|
6
|
-
import type { Prism } from '@tomorrowevening/theatre-dataverse';
|
|
7
|
-
import { val, Atom } from '@tomorrowevening/theatre-dataverse';
|
|
8
|
-
/**
|
|
9
|
-
* A React hook that executes the callback function and returns its return value
|
|
10
|
-
* whenever there's a change in the values of the dependency array, or in the
|
|
11
|
-
* prisms that are used within the callback function.
|
|
12
|
-
*
|
|
13
|
-
* @param fn - The callback function
|
|
14
|
-
* @param deps - The dependency array
|
|
15
|
-
* @param debugLabel - The label used by the debugger
|
|
16
|
-
*
|
|
17
|
-
* @remarks
|
|
18
|
-
*
|
|
19
|
-
* A common mistake with `usePrism()` is not including its deps in its dependency array. Let's
|
|
20
|
-
* have an eslint rule to catch that.
|
|
21
|
-
*/
|
|
22
|
-
export declare function usePrism<T>(fn: () => T, deps: unknown[], debugLabel?: string): T;
|
|
23
|
-
export declare const useVal: typeof val;
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @param
|
|
30
|
-
* @
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* ```
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*
|
|
121
|
-
* const ready = useVal(atom.pointer.ready)
|
|
122
|
-
* if (!ready) return <div>Loading...</div>
|
|
123
|
-
* return <button onClick={onClick}>Click me</button>
|
|
124
|
-
* }
|
|
125
|
-
* ```
|
|
126
|
-
*/
|
|
127
|
-
export declare function useAtom<T>(initialState: T): Atom<T>;
|
|
128
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
/**
|
|
2
|
+
* React bindings for dataverse.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import type { Prism } from '@tomorrowevening/theatre-dataverse';
|
|
7
|
+
import { val, Atom } from '@tomorrowevening/theatre-dataverse';
|
|
8
|
+
/**
|
|
9
|
+
* A React hook that executes the callback function and returns its return value
|
|
10
|
+
* whenever there's a change in the values of the dependency array, or in the
|
|
11
|
+
* prisms that are used within the callback function.
|
|
12
|
+
*
|
|
13
|
+
* @param fn - The callback function
|
|
14
|
+
* @param deps - The dependency array
|
|
15
|
+
* @param debugLabel - The label used by the debugger
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
*
|
|
19
|
+
* A common mistake with `usePrism()` is not including its deps in its dependency array. Let's
|
|
20
|
+
* have an eslint rule to catch that.
|
|
21
|
+
*/
|
|
22
|
+
export declare function usePrism<T>(fn: () => T, deps: unknown[], debugLabel?: string): T;
|
|
23
|
+
export declare const useVal: typeof val;
|
|
24
|
+
/**
|
|
25
|
+
* A React hook that returns the value of the prism that it received as the first argument.
|
|
26
|
+
* It works like an implementation of Dataverse's Ticker, except that it runs the side effects in
|
|
27
|
+
* an order where a component's prism is guaranteed to run before any of its descendents' prisms.
|
|
28
|
+
*
|
|
29
|
+
* @param der - The prism
|
|
30
|
+
* @param debugLabel - The label used by the debugger
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* It looks like this new implementation of usePrism() manages to:
|
|
34
|
+
* 1. Not over-calculate the prisms
|
|
35
|
+
* 2. Render prism in ancestor -\> descendent order
|
|
36
|
+
* 3. Not set off React's concurrent mode alarms
|
|
37
|
+
*
|
|
38
|
+
*
|
|
39
|
+
* I'm happy with how little bookkeeping we ended up doing here.
|
|
40
|
+
*
|
|
41
|
+
* ---
|
|
42
|
+
*
|
|
43
|
+
* Notes on the latest implementation:
|
|
44
|
+
*
|
|
45
|
+
* # Remove cold prism reads
|
|
46
|
+
*
|
|
47
|
+
* Prior to the latest change, the first render of every `usePrismInstance()` resulted in a cold read of its inner prism.
|
|
48
|
+
* Cold reads are predictably slow. The reason we'd run cold reads was to comply with react's rule of not running side-effects
|
|
49
|
+
* during render. (Turning a prism hot is _technically_ a side-effect).
|
|
50
|
+
*
|
|
51
|
+
* However, now that users are animating scenes with hundreds of objects in the same sequence, the lag started to be noticable.
|
|
52
|
+
*
|
|
53
|
+
* This commit changes `usePrismInstance()` so that it turns its prism hot before rendering them.
|
|
54
|
+
*
|
|
55
|
+
* # Freshen prisms before render
|
|
56
|
+
*
|
|
57
|
+
* Previously in order to avoid the zombie child problem (https://kaihao.dev/posts/stale-props-and-zombie-children-in-redux)
|
|
58
|
+
* we deferred freshening the prisms to the render phase of components. This meant that if a prism's dependencies
|
|
59
|
+
* changed, `usePrismInstance()` would schedule a re-render, regardless of whether that change actually affected the prism's
|
|
60
|
+
* value. Here is a contrived example:
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const num = new Box(1)
|
|
64
|
+
* const isPositiveD = prism(() => num.prism.getValue() >= 0)
|
|
65
|
+
*
|
|
66
|
+
* const Comp = () => {
|
|
67
|
+
* return <div>{usePrismInstance(isPositiveD)}</div>
|
|
68
|
+
* }
|
|
69
|
+
*
|
|
70
|
+
* num.set(2) // would cause Comp to re-render- even though 1 is still a positive number
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* We now avoid this problem by freshening the prism (i.e. calling `der.getValue()`) inside `runQueue()`,
|
|
74
|
+
* and then only causing a re-render if the prism's value is actually changed.
|
|
75
|
+
*
|
|
76
|
+
* This still avoids the zombie-child problem because `runQueue` reads the prisms in-order of their position in
|
|
77
|
+
* the mounting tree.
|
|
78
|
+
*
|
|
79
|
+
* On the off-chance that one of them still turns out to be a zombile child, `runQueue` will defer that particular
|
|
80
|
+
* `usePrismInstance()` to be read inside a normal react render phase.
|
|
81
|
+
*/
|
|
82
|
+
export declare function usePrismInstance<T>(der: Prism<T>, debugLabel?: string): T;
|
|
83
|
+
/**
|
|
84
|
+
* Creates a new Atom, similar to useState(), but the component
|
|
85
|
+
* won't re-render if the value of the atom changes.
|
|
86
|
+
*
|
|
87
|
+
* @param initialState - Initial state
|
|
88
|
+
* @returns The Atom
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
*
|
|
92
|
+
* Usage
|
|
93
|
+
* ```tsx
|
|
94
|
+
* import {useAtom, useVal} from '@theatre/react'
|
|
95
|
+
* import {useEffect} from 'react'
|
|
96
|
+
*
|
|
97
|
+
* function MyComponent() {
|
|
98
|
+
* const atom = useAtom({count: 0, ready: false})
|
|
99
|
+
*
|
|
100
|
+
* const onClick = () =>
|
|
101
|
+
* atom.setByPointer(
|
|
102
|
+
* (p) => p.count,
|
|
103
|
+
* (count) => count + 1,
|
|
104
|
+
* )
|
|
105
|
+
*
|
|
106
|
+
* useEffect(() => {
|
|
107
|
+
* setTimeout(() => {
|
|
108
|
+
* atom.setByPointer((p) => p.ready, true)
|
|
109
|
+
* }, 1000)
|
|
110
|
+
* }, [])
|
|
111
|
+
*
|
|
112
|
+
* const ready = useVal(atom.pointer.ready)
|
|
113
|
+
* if (!ready) return <div>Loading...</div>
|
|
114
|
+
* return <button onClick={onClick}>Click me</button>
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export declare function useAtom<T>(initialState: T): Atom<T>;
|
|
119
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,oCAAoC,CAAA;AAC7D,OAAO,EAAQ,GAAG,EAAE,IAAI,EAAC,MAAM,oCAAoC,CAAA;AAyBnE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,EAAE,EAAE,MAAM,CAAC,EACX,IAAI,EAAE,OAAO,EAAE,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,CAAC,CAmBH;AAED,eAAO,MAAM,MAAM,EAAE,OAAO,GAE3B,CAAA;AAsKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC,CAoEzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAMnD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
__markAsModule(target);
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __reExport = (target, module2, desc) => {
|
|
14
|
+
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(module2))
|
|
16
|
+
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
17
|
+
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return target;
|
|
20
|
+
};
|
|
21
|
+
var __toModule = (module2) => {
|
|
22
|
+
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/index.ts
|
|
26
|
+
__export(exports, {
|
|
27
|
+
useAtom: () => useAtom,
|
|
28
|
+
usePrism: () => usePrism,
|
|
29
|
+
usePrismInstance: () => usePrismInstance,
|
|
30
|
+
useVal: () => useVal
|
|
31
|
+
});
|
|
32
|
+
var import_theatre_dataverse = __toModule(require("@tomorrowevening/theatre-dataverse"));
|
|
33
|
+
var import_lodash_es = __toModule(require("lodash-es"));
|
|
34
|
+
var import_queue_microtask = __toModule(require("queue-microtask"));
|
|
35
|
+
var import_react = __toModule(require("react"));
|
|
36
|
+
var import_react_dom = __toModule(require("react-dom"));
|
|
37
|
+
var TRACE = false;
|
|
38
|
+
function useForceUpdate(debugLabel) {
|
|
39
|
+
const [, setTick] = (0, import_react.useState)(0);
|
|
40
|
+
const update = (0, import_react.useCallback)(() => {
|
|
41
|
+
setTick((tick) => tick + 1);
|
|
42
|
+
}, []);
|
|
43
|
+
return update;
|
|
44
|
+
}
|
|
45
|
+
function usePrism(fn, deps, debugLabel) {
|
|
46
|
+
const fnAsCallback = (0, import_react.useCallback)(fn, deps);
|
|
47
|
+
const atomRef = (0, import_react.useRef)(null);
|
|
48
|
+
if (!atomRef.current) {
|
|
49
|
+
atomRef.current = new import_theatre_dataverse.Atom(fnAsCallback);
|
|
50
|
+
} else {
|
|
51
|
+
atomRef.current.set(fnAsCallback);
|
|
52
|
+
}
|
|
53
|
+
const prismRef = (0, import_react.useRef)(null);
|
|
54
|
+
if (!prismRef.current) {
|
|
55
|
+
prismRef.current = (0, import_theatre_dataverse.prism)(() => {
|
|
56
|
+
const fn2 = atomRef.current.prism.getValue();
|
|
57
|
+
return fn2();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return usePrismInstance(prismRef.current, debugLabel);
|
|
61
|
+
}
|
|
62
|
+
var useVal = (p, debugLabel) => {
|
|
63
|
+
return usePrism(() => (0, import_theatre_dataverse.val)(p), [p], debugLabel);
|
|
64
|
+
};
|
|
65
|
+
var lastOrder = 0;
|
|
66
|
+
var queue = [];
|
|
67
|
+
var setOfQueuedItems = new Set();
|
|
68
|
+
var microtaskIsQueued = false;
|
|
69
|
+
var pushToQueue = (item) => {
|
|
70
|
+
_pushToQueue(item);
|
|
71
|
+
queueIfNeeded();
|
|
72
|
+
};
|
|
73
|
+
var _pushToQueue = (item) => {
|
|
74
|
+
if (setOfQueuedItems.has(item))
|
|
75
|
+
return;
|
|
76
|
+
setOfQueuedItems.add(item);
|
|
77
|
+
if (queue.length === 0) {
|
|
78
|
+
queue.push(item);
|
|
79
|
+
} else {
|
|
80
|
+
const index = (0, import_lodash_es.findIndex)(queue, (existingItem) => existingItem.order >= item.order);
|
|
81
|
+
if (index === -1) {
|
|
82
|
+
queue.push(item);
|
|
83
|
+
} else {
|
|
84
|
+
const right = queue[index];
|
|
85
|
+
if (right.order > item.order) {
|
|
86
|
+
queue.splice(index, 0, item);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var removeFromQueue = (item) => {
|
|
92
|
+
if (!setOfQueuedItems.has(item))
|
|
93
|
+
return;
|
|
94
|
+
setOfQueuedItems.delete(item);
|
|
95
|
+
const index = (0, import_lodash_es.findIndex)(queue, (o) => o === item);
|
|
96
|
+
queue.splice(index, 1);
|
|
97
|
+
};
|
|
98
|
+
function queueIfNeeded() {
|
|
99
|
+
if (microtaskIsQueued)
|
|
100
|
+
return;
|
|
101
|
+
microtaskIsQueued = true;
|
|
102
|
+
(0, import_queue_microtask.default)(() => {
|
|
103
|
+
(0, import_react_dom.unstable_batchedUpdates)(function runQueue() {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
while (queue.length > 0) {
|
|
106
|
+
const item = queue.shift();
|
|
107
|
+
setOfQueuedItems.delete(item);
|
|
108
|
+
let newValue;
|
|
109
|
+
if (TRACE) {
|
|
110
|
+
(_a = item.debug) == null ? void 0 : _a.history.push(`queue reached`);
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
newValue = item.der.getValue();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (TRACE) {
|
|
116
|
+
(_b = item.debug) == null ? void 0 : _b.history.push(`queue: der.getValue() errored`);
|
|
117
|
+
}
|
|
118
|
+
console.error("A `der.getValue()` in `usePrismInstance(der)` threw an error. This may be a zombie child issue, so we're gonna try to get its value again in a normal react render phase.If you see the same error again, then you either have an error in your prism code, or the deps array in `usePrism(fn, deps)` is missing a dependency and causing the prism to read stale values.");
|
|
119
|
+
console.error(error);
|
|
120
|
+
item.runUpdate();
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (newValue !== item.lastValue) {
|
|
124
|
+
item.lastValue = newValue;
|
|
125
|
+
item.runUpdate();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
microtaskIsQueued = false;
|
|
129
|
+
}, 1);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function usePrismInstance(der, debugLabel) {
|
|
133
|
+
var _a;
|
|
134
|
+
const _forceUpdate = useForceUpdate(debugLabel);
|
|
135
|
+
const ref = (0, import_react.useRef)(void 0);
|
|
136
|
+
if (!ref.current) {
|
|
137
|
+
lastOrder++;
|
|
138
|
+
ref.current = {
|
|
139
|
+
order: lastOrder,
|
|
140
|
+
runUpdate: () => {
|
|
141
|
+
if (!ref.current.unmounted) {
|
|
142
|
+
_forceUpdate();
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
der,
|
|
146
|
+
lastValue: void 0,
|
|
147
|
+
unmounted: false,
|
|
148
|
+
queueUpdate: () => {
|
|
149
|
+
var _a2;
|
|
150
|
+
if (TRACE) {
|
|
151
|
+
(_a2 = ref.current.debug) == null ? void 0 : _a2.history.push(`queueUpdate()`);
|
|
152
|
+
}
|
|
153
|
+
pushToQueue(ref.current);
|
|
154
|
+
},
|
|
155
|
+
untap: der.onStale(() => {
|
|
156
|
+
if (TRACE) {
|
|
157
|
+
ref.current.debug.history.push(`onStale(cb)`);
|
|
158
|
+
}
|
|
159
|
+
ref.current.queueUpdate();
|
|
160
|
+
})
|
|
161
|
+
};
|
|
162
|
+
if (TRACE) {
|
|
163
|
+
ref.current.debug = {
|
|
164
|
+
label: debugLabel,
|
|
165
|
+
traceOfFirstTimeRender: new Error(),
|
|
166
|
+
history: []
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (process.env.NODE_ENV !== "production") {
|
|
171
|
+
if (der !== ref.current.der) {
|
|
172
|
+
console.error("Argument `der` in `usePrismInstance(der)` should not change between renders.");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
176
|
+
return function onUnmount() {
|
|
177
|
+
ref.current.unmounted = true;
|
|
178
|
+
ref.current.untap();
|
|
179
|
+
removeFromQueue(ref.current);
|
|
180
|
+
};
|
|
181
|
+
}, []);
|
|
182
|
+
removeFromQueue(ref.current);
|
|
183
|
+
const newValue = ref.current.der.getValue();
|
|
184
|
+
ref.current.lastValue = newValue;
|
|
185
|
+
if (TRACE) {
|
|
186
|
+
(_a = ref.current.debug) == null ? void 0 : _a.history.push(`rendered`);
|
|
187
|
+
}
|
|
188
|
+
return newValue;
|
|
189
|
+
}
|
|
190
|
+
function useAtom(initialState) {
|
|
191
|
+
const ref = (0, import_react.useRef)(void 0);
|
|
192
|
+
if (!ref.current) {
|
|
193
|
+
ref.current = new import_theatre_dataverse.Atom(initialState);
|
|
194
|
+
}
|
|
195
|
+
return ref.current;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\r\n * React bindings for dataverse.\r\n *\r\n * @packageDocumentation\r\n */\r\n\r\nimport type {Prism} from '@tomorrowevening/theatre-dataverse'\r\nimport {prism, val, Atom} from '@tomorrowevening/theatre-dataverse'\r\nimport {findIndex} from 'lodash-es'\r\nimport queueMicrotask from 'queue-microtask'\r\nimport {useCallback, useLayoutEffect, useRef, useState} from 'react'\r\nimport {unstable_batchedUpdates} from 'react-dom'\r\n\r\ntype $IntentionalAny = any\r\ntype VoidFn = () => void\r\n\r\n/**\r\n * Enables a few traces and debug points to help identify performance glitches in `@theatre/react`.\r\n * Look up references to this value to see how to make use of those traces.\r\n */\r\nconst TRACE: boolean = false && process.env.NODE_ENV !== 'production'\r\n\r\nfunction useForceUpdate(debugLabel?: string) {\r\n const [, setTick] = useState(0)\r\n\r\n const update = useCallback(() => {\r\n setTick((tick) => tick + 1)\r\n }, [])\r\n\r\n return update\r\n}\r\n\r\n/**\r\n * A React hook that executes the callback function and returns its return value\r\n * whenever there's a change in the values of the dependency array, or in the\r\n * prisms that are used within the callback function.\r\n *\r\n * @param fn - The callback function\r\n * @param deps - The dependency array\r\n * @param debugLabel - The label used by the debugger\r\n *\r\n * @remarks\r\n *\r\n * A common mistake with `usePrism()` is not including its deps in its dependency array. Let's\r\n * have an eslint rule to catch that.\r\n */\r\nexport function usePrism<T>(\r\n fn: () => T,\r\n deps: unknown[],\r\n debugLabel?: string,\r\n): T {\r\n const fnAsCallback = useCallback(fn, deps)\r\n const atomRef = useRef<Atom<typeof fn>>(null as $IntentionalAny)\r\n if (!atomRef.current) {\r\n atomRef.current = new Atom(fnAsCallback)\r\n } else {\r\n atomRef.current.set(fnAsCallback)\r\n }\r\n\r\n const prismRef = useRef<Prism<T> | null>(null)\r\n\r\n if (!prismRef.current) {\r\n prismRef.current = prism(() => {\r\n const fn = atomRef.current.prism.getValue()\r\n return fn()\r\n })\r\n }\r\n\r\n return usePrismInstance(prismRef.current, debugLabel)\r\n}\r\n\r\nexport const useVal: typeof val = (p: $IntentionalAny, debugLabel?: string) => {\r\n return usePrism(() => val(p), [p], debugLabel)\r\n}\r\n\r\n/**\r\n * Each usePrism() call is assigned an `order`. Parents have a smaller\r\n * order than their children, and so on.\r\n */\r\nlet lastOrder = 0\r\n\r\n/**\r\n * A sorted array of prisms that need to be refreshed. The prisms are sorted\r\n * by their order, which means a parent prism always gets priority to children\r\n * and descendents. Ie. we refresh the prisms top to bottom.\r\n */\r\nconst queue: QueueItem[] = []\r\nconst setOfQueuedItems = new Set<QueueItem>()\r\n\r\ntype QueueItem<T = unknown> = {\r\n order: number\r\n /**\r\n * runUpdate() is the equivalent of a forceUpdate() call. It would only be called\r\n * if the value of the inner prism has _actually_ changed.\r\n */\r\n runUpdate: VoidFn\r\n /**\r\n * Some debugging info that are only present if {@link TRACE} is true\r\n */\r\n debug?: {\r\n /**\r\n * The `debugLabel` given to `usePrism()/usePrismInstance()`\r\n */\r\n label?: string\r\n /**\r\n * A trace of the first time the component got rendered\r\n */\r\n traceOfFirstTimeRender: Error\r\n /**\r\n * An array of the operations done on/about this usePrismInstance. This is helpful to trace\r\n * why a usePrismInstance's update was added to the queue and why it re-rendered\r\n */\r\n history: Array<\r\n /**\r\n * Item reached its turn in the queue\r\n */\r\n | `queue reached`\r\n /**\r\n * Item reached its turn in the queue, and errored (likely something in `prism()` threw an error)\r\n */\r\n | `queue: der.getValue() errored`\r\n /**\r\n * The item was added to the queue (may be called multiple times, but will only queue once)\r\n */\r\n | `queueUpdate()`\r\n /**\r\n * `cb` in `item.der.onStale(cb)` was called\r\n */\r\n | `onStale(cb)`\r\n /**\r\n * Item was rendered\r\n */\r\n | `rendered`\r\n >\r\n }\r\n /**\r\n * A reference to the prism\r\n */\r\n der: Prism<T>\r\n /**\r\n * The last value of this prism.\r\n */\r\n lastValue: T\r\n /**\r\n * Would be set to true if the element hosting the `usePrismInstance()` was unmounted\r\n */\r\n unmounted: boolean\r\n /**\r\n * Adds the `usePrismInstance` to the update queue\r\n */\r\n queueUpdate: () => void\r\n /**\r\n * Untaps from `this.der.unStale()`\r\n */\r\n untap: () => void\r\n}\r\n\r\nlet microtaskIsQueued = false\r\n\r\nconst pushToQueue = (item: QueueItem) => {\r\n _pushToQueue(item)\r\n queueIfNeeded()\r\n}\r\n\r\nconst _pushToQueue = (item: QueueItem) => {\r\n if (setOfQueuedItems.has(item)) return\r\n setOfQueuedItems.add(item)\r\n\r\n if (queue.length === 0) {\r\n queue.push(item)\r\n } else {\r\n const index = findIndex(\r\n queue,\r\n (existingItem) => existingItem.order >= item.order,\r\n )\r\n if (index === -1) {\r\n queue.push(item)\r\n } else {\r\n const right = queue[index]\r\n if (right.order > item.order) {\r\n queue.splice(index, 0, item)\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Plucks items from the queue\r\n */\r\nconst removeFromQueue = (item: QueueItem) => {\r\n if (!setOfQueuedItems.has(item)) return\r\n setOfQueuedItems.delete(item)\r\n\r\n const index = findIndex(queue, (o) => o === item)\r\n queue.splice(index, 1)\r\n}\r\n\r\nfunction queueIfNeeded() {\r\n if (microtaskIsQueued) return\r\n microtaskIsQueued = true\r\n\r\n queueMicrotask(() => {\r\n unstable_batchedUpdates(function runQueue() {\r\n while (queue.length > 0) {\r\n const item = queue.shift()!\r\n setOfQueuedItems.delete(item)\r\n\r\n let newValue\r\n if (TRACE) {\r\n item.debug?.history.push(`queue reached`)\r\n }\r\n try {\r\n newValue = item.der.getValue()\r\n } catch (error) {\r\n if (TRACE) {\r\n item.debug?.history.push(`queue: der.getValue() errored`)\r\n }\r\n console.error(\r\n 'A `der.getValue()` in `usePrismInstance(der)` threw an error. ' +\r\n \"This may be a zombie child issue, so we're gonna try to get its value again in a normal react render phase.\" +\r\n 'If you see the same error again, then you either have an error in your prism code, or the deps array in `usePrism(fn, deps)` is missing ' +\r\n 'a dependency and causing the prism to read stale values.',\r\n )\r\n console.error(error)\r\n\r\n item.runUpdate()\r\n\r\n continue\r\n }\r\n if (newValue !== item.lastValue) {\r\n item.lastValue = newValue\r\n item.runUpdate()\r\n }\r\n }\r\n\r\n microtaskIsQueued = false\r\n }, 1)\r\n })\r\n}\r\n/**\r\n * A React hook that returns the value of the prism that it received as the first argument.\r\n * It works like an implementation of Dataverse's Ticker, except that it runs the side effects in\r\n * an order where a component's prism is guaranteed to run before any of its descendents' prisms.\r\n *\r\n * @param der - The prism\r\n * @param debugLabel - The label used by the debugger\r\n *\r\n * @remarks\r\n * It looks like this new implementation of usePrism() manages to:\r\n * 1. Not over-calculate the prisms\r\n * 2. Render prism in ancestor -\\> descendent order\r\n * 3. Not set off React's concurrent mode alarms\r\n *\r\n *\r\n * I'm happy with how little bookkeeping we ended up doing here.\r\n *\r\n * ---\r\n *\r\n * Notes on the latest implementation:\r\n *\r\n * # Remove cold prism reads\r\n *\r\n * Prior to the latest change, the first render of every `usePrismInstance()` resulted in a cold read of its inner prism.\r\n * Cold reads are predictably slow. The reason we'd run cold reads was to comply with react's rule of not running side-effects\r\n * during render. (Turning a prism hot is _technically_ a side-effect).\r\n *\r\n * However, now that users are animating scenes with hundreds of objects in the same sequence, the lag started to be noticable.\r\n *\r\n * This commit changes `usePrismInstance()` so that it turns its prism hot before rendering them.\r\n *\r\n * # Freshen prisms before render\r\n *\r\n * Previously in order to avoid the zombie child problem (https://kaihao.dev/posts/stale-props-and-zombie-children-in-redux)\r\n * we deferred freshening the prisms to the render phase of components. This meant that if a prism's dependencies\r\n * changed, `usePrismInstance()` would schedule a re-render, regardless of whether that change actually affected the prism's\r\n * value. Here is a contrived example:\r\n *\r\n * ```ts\r\n * const num = new Box(1)\r\n * const isPositiveD = prism(() => num.prism.getValue() >= 0)\r\n *\r\n * const Comp = () => {\r\n * return <div>{usePrismInstance(isPositiveD)}</div>\r\n * }\r\n *\r\n * num.set(2) // would cause Comp to re-render- even though 1 is still a positive number\r\n * ```\r\n *\r\n * We now avoid this problem by freshening the prism (i.e. calling `der.getValue()`) inside `runQueue()`,\r\n * and then only causing a re-render if the prism's value is actually changed.\r\n *\r\n * This still avoids the zombie-child problem because `runQueue` reads the prisms in-order of their position in\r\n * the mounting tree.\r\n *\r\n * On the off-chance that one of them still turns out to be a zombile child, `runQueue` will defer that particular\r\n * `usePrismInstance()` to be read inside a normal react render phase.\r\n */\r\nexport function usePrismInstance<T>(der: Prism<T>, debugLabel?: string): T {\r\n const _forceUpdate = useForceUpdate(debugLabel)\r\n\r\n const ref = useRef<QueueItem<T>>(undefined as $IntentionalAny)\r\n\r\n if (!ref.current) {\r\n lastOrder++\r\n\r\n ref.current = {\r\n order: lastOrder,\r\n runUpdate: () => {\r\n if (!ref.current.unmounted) {\r\n _forceUpdate()\r\n }\r\n },\r\n der,\r\n lastValue: undefined as $IntentionalAny,\r\n unmounted: false,\r\n queueUpdate: () => {\r\n if (TRACE) {\r\n ref.current.debug?.history.push(`queueUpdate()`)\r\n }\r\n pushToQueue(ref.current)\r\n },\r\n untap: der.onStale(() => {\r\n if (TRACE) {\r\n ref.current.debug!.history.push(`onStale(cb)`)\r\n }\r\n ref.current!.queueUpdate()\r\n }),\r\n }\r\n\r\n if (TRACE) {\r\n ref.current.debug = {\r\n label: debugLabel,\r\n traceOfFirstTimeRender: new Error(),\r\n history: [],\r\n }\r\n }\r\n }\r\n\r\n if (process.env.NODE_ENV !== 'production') {\r\n if (der !== ref.current.der) {\r\n console.error(\r\n 'Argument `der` in `usePrismInstance(der)` should not change between renders.',\r\n )\r\n }\r\n }\r\n\r\n useLayoutEffect(() => {\r\n return function onUnmount() {\r\n ref.current.unmounted = true\r\n ref.current.untap()\r\n removeFromQueue(ref.current)\r\n }\r\n }, [])\r\n\r\n // if we're queued but are rendering before our turn, remove us from the queue\r\n removeFromQueue(ref.current)\r\n\r\n const newValue = ref.current.der.getValue()\r\n ref.current.lastValue = newValue\r\n\r\n if (TRACE) {\r\n ref.current.debug?.history.push(`rendered`)\r\n }\r\n\r\n return newValue\r\n}\r\n\r\n/**\r\n * Creates a new Atom, similar to useState(), but the component\r\n * won't re-render if the value of the atom changes.\r\n *\r\n * @param initialState - Initial state\r\n * @returns The Atom\r\n *\r\n * @example\r\n *\r\n * Usage\r\n * ```tsx\r\n * import {useAtom, useVal} from '@theatre/react'\r\n * import {useEffect} from 'react'\r\n *\r\n * function MyComponent() {\r\n * const atom = useAtom({count: 0, ready: false})\r\n *\r\n * const onClick = () =>\r\n * atom.setByPointer(\r\n * (p) => p.count,\r\n * (count) => count + 1,\r\n * )\r\n *\r\n * useEffect(() => {\r\n * setTimeout(() => {\r\n * atom.setByPointer((p) => p.ready, true)\r\n * }, 1000)\r\n * }, [])\r\n *\r\n * const ready = useVal(atom.pointer.ready)\r\n * if (!ready) return <div>Loading...</div>\r\n * return <button onClick={onClick}>Click me</button>\r\n * }\r\n * ```\r\n */\r\nexport function useAtom<T>(initialState: T): Atom<T> {\r\n const ref = useRef<Atom<T>>(undefined as $IntentionalAny)\r\n if (!ref.current) {\r\n ref.current = new Atom(initialState)\r\n }\r\n return ref.current\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.18.11"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowevening/theatre-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "
|
|
7
|
-
"email": "
|
|
8
|
-
"url": "https://
|
|
6
|
+
"name": "Aria Minaei",
|
|
7
|
+
"email": "aria@theatrejs.com",
|
|
8
|
+
"url": "https://github.com/AriaMinaei"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/
|
|
12
|
+
"url": "https://github.com/AriaMinaei/theatre",
|
|
13
13
|
"directory": "packages/react"
|
|
14
14
|
},
|
|
15
15
|
"main": "dist/index.js",
|
|
@@ -17,15 +17,12 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist/**/*"
|
|
19
19
|
],
|
|
20
|
-
"publishConfig": {
|
|
21
|
-
"access": "public"
|
|
22
|
-
},
|
|
23
20
|
"scripts": {
|
|
24
21
|
"prepack": "node ../../devEnv/ensurePublishing.js",
|
|
25
22
|
"typecheck": "yarn run build",
|
|
26
23
|
"build": "run-s build:ts build:js build:api-json",
|
|
27
24
|
"build:ts": "tsc --build ./tsconfig.json",
|
|
28
|
-
"build:js": "
|
|
25
|
+
"build:js": "node -r esbuild-register ./devEnv/build.ts",
|
|
29
26
|
"build:api-json": "api-extractor run --local --config devEnv/api-extractor.json",
|
|
30
27
|
"prepublish": "node ../../devEnv/ensurePublishing.js",
|
|
31
28
|
"clean": "rm -rf ./dist && rm -f tsconfig.tsbuildinfo"
|
|
@@ -38,8 +35,8 @@
|
|
|
38
35
|
"@types/react": "^17.0.9",
|
|
39
36
|
"@types/react-dom": "^17.0.6",
|
|
40
37
|
"esbuild": "^0.12.15",
|
|
38
|
+
"esbuild-register": "^2.5.0",
|
|
41
39
|
"npm-run-all": "^4.1.5",
|
|
42
|
-
"tsx": "4.7.0",
|
|
43
40
|
"typescript": "5.1.6"
|
|
44
41
|
},
|
|
45
42
|
"dependencies": {
|