@tldraw/state-react 3.16.0-internal.a478398270c6 → 3.16.0-internal.f8b97f0c414f

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-cjs/index.js CHANGED
@@ -37,7 +37,7 @@ var import_useStateTracking = require("./lib/useStateTracking");
37
37
  var import_useValue = require("./lib/useValue");
38
38
  (0, import_utils.registerTldrawLibraryVersion)(
39
39
  "@tldraw/state-react",
40
- "3.16.0-internal.a478398270c6",
40
+ "3.16.0-internal.f8b97f0c414f",
41
41
  "cjs"
42
42
  );
43
43
  //# sourceMappingURL=index.js.map
@@ -22,27 +22,22 @@ __export(useReactor_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(useReactor_exports);
24
24
  var import_state = require("@tldraw/state");
25
+ var import_utils = require("@tldraw/utils");
25
26
  var import_react = require("react");
26
27
  function useReactor(name, reactFn, deps = []) {
27
- const raf = (0, import_react.useRef)(-1);
28
- const scheduler = (0, import_react.useMemo)(
29
- () => new import_state.EffectScheduler(name, reactFn, {
28
+ (0, import_react.useEffect)(() => {
29
+ let cancelFn;
30
+ const scheduler = new import_state.EffectScheduler(name, reactFn, {
30
31
  scheduleEffect: (cb) => {
31
- const rafId = requestAnimationFrame(cb);
32
- raf.current = rafId;
33
- return rafId;
32
+ cancelFn = (0, import_utils.throttleToNextFrame)(cb);
34
33
  }
35
- }),
36
- // eslint-disable-next-line react-hooks/exhaustive-deps
37
- deps
38
- );
39
- (0, import_react.useEffect)(() => {
34
+ });
40
35
  scheduler.attach();
41
36
  scheduler.execute();
42
37
  return () => {
43
38
  scheduler.detach();
44
- cancelAnimationFrame(raf.current);
39
+ cancelFn?.();
45
40
  };
46
- }, [scheduler]);
41
+ }, deps);
47
42
  }
48
43
  //# sourceMappingURL=useReactor.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useReactor.ts"],
4
- "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { useEffect, useMemo, useRef } from 'react'\n\n/** @public */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tconst raf = useRef(-1)\n\tconst scheduler = useMemo(\n\t\t() =>\n\t\t\tnew EffectScheduler(name, reactFn, {\n\t\t\t\tscheduleEffect: (cb) => {\n\t\t\t\t\tconst rafId = requestAnimationFrame(cb)\n\t\t\t\t\traf.current = rafId\n\t\t\t\t\treturn rafId\n\t\t\t\t},\n\t\t\t}),\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\tdeps\n\t)\n\n\tuseEffect(() => {\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelAnimationFrame(raf.current)\n\t\t}\n\t}, [scheduler])\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAChC,mBAA2C;AAGpC,SAAS,WAAW,MAAc,SAAqB,OAA0B,CAAC,GAAG;AAC3F,QAAM,UAAM,qBAAO,EAAE;AACrB,QAAM,gBAAY;AAAA,IACjB,MACC,IAAI,6BAAgB,MAAM,SAAS;AAAA,MAClC,gBAAgB,CAAC,OAAO;AACvB,cAAM,QAAQ,sBAAsB,EAAE;AACtC,YAAI,UAAU;AACd,eAAO;AAAA,MACR;AAAA,IACD,CAAC;AAAA;AAAA,IAEF;AAAA,EACD;AAEA,8BAAU,MAAM;AACf,cAAU,OAAO;AACjB,cAAU,QAAQ;AAClB,WAAO,MAAM;AACZ,gBAAU,OAAO;AACjB,2BAAqB,IAAI,OAAO;AAAA,IACjC;AAAA,EACD,GAAG,CAAC,SAAS,CAAC;AACf;",
4
+ "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { throttleToNextFrame } from '@tldraw/utils'\nimport { useEffect } from 'react'\n\n/** @public */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tuseEffect(() => {\n\t\tlet cancelFn: () => void | undefined\n\t\tconst scheduler = new EffectScheduler(name, reactFn, {\n\t\t\tscheduleEffect: (cb) => {\n\t\t\t\tcancelFn = throttleToNextFrame(cb)\n\t\t\t},\n\t\t})\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelFn?.()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAChC,mBAAoC;AACpC,mBAA0B;AAGnB,SAAS,WAAW,MAAc,SAAqB,OAA0B,CAAC,GAAG;AAC3F,8BAAU,MAAM;AACf,QAAI;AACJ,UAAM,YAAY,IAAI,6BAAgB,MAAM,SAAS;AAAA,MACpD,gBAAgB,CAAC,OAAO;AACvB,uBAAW,kCAAoB,EAAE;AAAA,MAClC;AAAA,IACD,CAAC;AACD,cAAU,OAAO;AACjB,cAAU,QAAQ;AAClB,WAAO,MAAM;AACZ,gBAAU,OAAO;AACjB,iBAAW;AAAA,IACZ;AAAA,EAED,GAAG,IAAI;AACR;",
6
6
  "names": []
7
7
  }
@@ -27,39 +27,23 @@ function useValue() {
27
27
  const args = arguments;
28
28
  const deps = args.length === 3 ? args[2] : [args[0]];
29
29
  const name = args.length === 3 ? args[0] : `useValue(${args[0].name})`;
30
- const isInRender = (0, import_react.useRef)(true);
31
- isInRender.current = true;
32
- const $val = (0, import_react.useMemo)(() => {
33
- if (args.length === 1) {
34
- return args[0];
35
- }
36
- return (0, import_state.computed)(name, () => {
37
- if (isInRender.current) {
38
- return args[1]();
39
- } else {
40
- try {
41
- return args[1]();
42
- } catch {
43
- return {};
44
- }
45
- }
46
- });
30
+ const { $val, subscribe, getSnapshot } = (0, import_react.useMemo)(() => {
31
+ const $val2 = args.length === 1 ? args[0] : (0, import_state.computed)(name, args[1]);
32
+ return {
33
+ $val: $val2,
34
+ subscribe: (notify) => {
35
+ return (0, import_state.react)(`useValue(${name})`, () => {
36
+ try {
37
+ $val2.get();
38
+ } catch {
39
+ }
40
+ notify();
41
+ });
42
+ },
43
+ getSnapshot: () => $val2.lastChangedEpoch
44
+ };
47
45
  }, deps);
48
- try {
49
- const { subscribe, getSnapshot } = (0, import_react.useMemo)(() => {
50
- return {
51
- subscribe: (listen) => {
52
- return (0, import_state.react)(`useValue(${name})`, () => {
53
- $val.get();
54
- listen();
55
- });
56
- },
57
- getSnapshot: () => $val.get()
58
- };
59
- }, [$val]);
60
- return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
61
- } finally {
62
- isInRender.current = false;
63
- }
46
+ (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
47
+ return $val.__unsafe__getWithoutCapture();
64
48
  }
65
49
  //# sourceMappingURL=useValue.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useValue.ts"],
4
- "sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Signal, computed, react } from '@tldraw/state'\nimport { useMemo, useRef, useSyncExternalStore } from 'react'\n\n/** @public */\nexport function useValue<Value>(value: Signal<Value>): Value\n\n/**\n * Extracts the value from a signal and subscribes to it.\n *\n * Note that you do not need to use this hook if you are wrapping the component with {@link track}\n *\n * @example\n * ```ts\n * const Counter: React.FC = () => {\n * const $count = useAtom('count', 0)\n * const increment = useCallback(() => $count.set($count.get() + 1), [count])\n * const currentCount = useValue($count)\n * return <button onClick={increment}>{currentCount}</button>\n * }\n * ```\n *\n * You can also pass a function to compute the value and it will be memoized as in `useComputed`:\n *\n * @example\n * ```ts\n * type GreeterProps = {\n * firstName: Signal<string>\n * lastName: Signal<string>\n * }\n *\n * const Greeter = track(function Greeter({ firstName, lastName }: GreeterProps) {\n * const fullName = useValue('fullName', () => `${firstName.get()} ${lastName.get()}`, [\n * firstName,\n * lastName,\n * ])\n * return <div>Hello {fullName}!</div>\n * })\n * ```\n *\n * @public\n */\nexport function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value\n\n/** @public */\nexport function useValue() {\n\tconst args = arguments\n\t// deps will be either the computed or the deps array\n\tconst deps = args.length === 3 ? args[2] : [args[0]]\n\tconst name = args.length === 3 ? args[0] : `useValue(${args[0].name})`\n\n\tconst isInRender = useRef(true)\n\tisInRender.current = true\n\n\tconst $val = useMemo(() => {\n\t\tif (args.length === 1) {\n\t\t\treturn args[0]\n\t\t}\n\t\treturn computed(name, () => {\n\t\t\tif (isInRender.current) {\n\t\t\t\treturn args[1]()\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\treturn args[1]()\n\t\t\t\t} catch {\n\t\t\t\t\t// when getSnapshot is called outside of the render phase &\n\t\t\t\t\t// subsequently throws an error, it might be because we're\n\t\t\t\t\t// in a zombie-child state. in that case, we suppress the\n\t\t\t\t\t// error and instead return a new dummy value to trigger a\n\t\t\t\t\t// react re-render. if we were in a zombie child, react will\n\t\t\t\t\t// unmount us instead of re-rendering so the error is\n\t\t\t\t\t// irrelevant. if we're not in a zombie-child, react will\n\t\t\t\t\t// call `getSnapshot` again in the render phase, and the\n\t\t\t\t\t// error will be thrown as expected.\n\t\t\t\t\treturn {}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n\n\ttry {\n\t\tconst { subscribe, getSnapshot } = useMemo(() => {\n\t\t\treturn {\n\t\t\t\tsubscribe: (listen: () => void) => {\n\t\t\t\t\treturn react(`useValue(${name})`, () => {\n\t\t\t\t\t\t$val.get()\n\t\t\t\t\t\tlisten()\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tgetSnapshot: () => $val.get(),\n\t\t\t}\n\t\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t}, [$val])\n\n\t\treturn useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\t} finally {\n\t\tisInRender.current = false\n\t}\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAwC;AACxC,mBAAsD;AA2C/C,SAAS,WAAW;AAC1B,QAAM,OAAO;AAEb,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnD,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,EAAE,IAAI;AAEnE,QAAM,iBAAa,qBAAO,IAAI;AAC9B,aAAW,UAAU;AAErB,QAAM,WAAO,sBAAQ,MAAM;AAC1B,QAAI,KAAK,WAAW,GAAG;AACtB,aAAO,KAAK,CAAC;AAAA,IACd;AACA,eAAO,uBAAS,MAAM,MAAM;AAC3B,UAAI,WAAW,SAAS;AACvB,eAAO,KAAK,CAAC,EAAE;AAAA,MAChB,OAAO;AACN,YAAI;AACH,iBAAO,KAAK,CAAC,EAAE;AAAA,QAChB,QAAQ;AAUP,iBAAO,CAAC;AAAA,QACT;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EAEF,GAAG,IAAI;AAEP,MAAI;AACH,UAAM,EAAE,WAAW,YAAY,QAAI,sBAAQ,MAAM;AAChD,aAAO;AAAA,QACN,WAAW,CAAC,WAAuB;AAClC,qBAAO,oBAAM,YAAY,IAAI,KAAK,MAAM;AACvC,iBAAK,IAAI;AACT,mBAAO;AAAA,UACR,CAAC;AAAA,QACF;AAAA,QACA,aAAa,MAAM,KAAK,IAAI;AAAA,MAC7B;AAAA,IAED,GAAG,CAAC,IAAI,CAAC;AAET,eAAO,mCAAqB,WAAW,aAAa,WAAW;AAAA,EAChE,UAAE;AACD,eAAW,UAAU;AAAA,EACtB;AACD;",
6
- "names": []
4
+ "sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Signal, computed, react } from '@tldraw/state'\nimport { useMemo, useSyncExternalStore } from 'react'\n\n/** @public */\nexport function useValue<Value>(value: Signal<Value>): Value\n\n/**\n * Extracts the value from a signal and subscribes to it.\n *\n * Note that you do not need to use this hook if you are wrapping the component with {@link track}\n *\n * @example\n * ```ts\n * const Counter: React.FC = () => {\n * const $count = useAtom('count', 0)\n * const increment = useCallback(() => $count.set($count.get() + 1), [count])\n * const currentCount = useValue($count)\n * return <button onClick={increment}>{currentCount}</button>\n * }\n * ```\n *\n * You can also pass a function to compute the value and it will be memoized as in `useComputed`:\n *\n * @example\n * ```ts\n * type GreeterProps = {\n * firstName: Signal<string>\n * lastName: Signal<string>\n * }\n *\n * const Greeter = track(function Greeter({ firstName, lastName }: GreeterProps) {\n * const fullName = useValue('fullName', () => `${firstName.get()} ${lastName.get()}`, [\n * firstName,\n * lastName,\n * ])\n * return <div>Hello {fullName}!</div>\n * })\n * ```\n *\n * @public\n */\nexport function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value\n\n/** @public */\nexport function useValue() {\n\tconst args = arguments\n\t// deps will be either the computed or the deps array\n\tconst deps = args.length === 3 ? args[2] : [args[0]]\n\tconst name = args.length === 3 ? args[0] : `useValue(${args[0].name})`\n\n\tconst { $val, subscribe, getSnapshot } = useMemo(() => {\n\t\tconst $val =\n\t\t\targs.length === 1 ? (args[0] as Signal<any>) : (computed(name, args[1]) as Signal<any>)\n\n\t\treturn {\n\t\t\t$val,\n\t\t\tsubscribe: (notify: () => void) => {\n\t\t\t\treturn react(`useValue(${name})`, () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t$val.get()\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Will be rethrown during render if the component doesn't unmount first.\n\t\t\t\t\t}\n\t\t\t\t\tnotify()\n\t\t\t\t})\n\t\t\t},\n\t\t\tgetSnapshot: () => $val.lastChangedEpoch,\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n\n\tuseSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\treturn $val.__unsafe__getWithoutCapture()\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAwC;AACxC,mBAA8C;AA2CvC,SAAS,WAAW;AAC1B,QAAM,OAAO;AAEb,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnD,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,EAAE,IAAI;AAEnE,QAAM,EAAE,MAAM,WAAW,YAAY,QAAI,sBAAQ,MAAM;AACtD,UAAMA,QACL,KAAK,WAAW,IAAK,KAAK,CAAC,QAAqB,uBAAS,MAAM,KAAK,CAAC,CAAC;AAEvE,WAAO;AAAA,MACN,MAAAA;AAAA,MACA,WAAW,CAAC,WAAuB;AAClC,mBAAO,oBAAM,YAAY,IAAI,KAAK,MAAM;AACvC,cAAI;AACH,YAAAA,MAAK,IAAI;AAAA,UACV,QAAQ;AAAA,UAER;AACA,iBAAO;AAAA,QACR,CAAC;AAAA,MACF;AAAA,MACA,aAAa,MAAMA,MAAK;AAAA,IACzB;AAAA,EAED,GAAG,IAAI;AAEP,yCAAqB,WAAW,aAAa,WAAW;AACxD,SAAO,KAAK,4BAA4B;AACzC;",
6
+ "names": ["$val"]
7
7
  }
@@ -8,7 +8,7 @@ import { useStateTracking } from "./lib/useStateTracking.mjs";
8
8
  import { useValue } from "./lib/useValue.mjs";
9
9
  registerTldrawLibraryVersion(
10
10
  "@tldraw/state-react",
11
- "3.16.0-internal.a478398270c6",
11
+ "3.16.0-internal.f8b97f0c414f",
12
12
  "esm"
13
13
  );
14
14
  export {
@@ -1,26 +1,21 @@
1
1
  import { EffectScheduler } from "@tldraw/state";
2
- import { useEffect, useMemo, useRef } from "react";
2
+ import { throttleToNextFrame } from "@tldraw/utils";
3
+ import { useEffect } from "react";
3
4
  function useReactor(name, reactFn, deps = []) {
4
- const raf = useRef(-1);
5
- const scheduler = useMemo(
6
- () => new EffectScheduler(name, reactFn, {
5
+ useEffect(() => {
6
+ let cancelFn;
7
+ const scheduler = new EffectScheduler(name, reactFn, {
7
8
  scheduleEffect: (cb) => {
8
- const rafId = requestAnimationFrame(cb);
9
- raf.current = rafId;
10
- return rafId;
9
+ cancelFn = throttleToNextFrame(cb);
11
10
  }
12
- }),
13
- // eslint-disable-next-line react-hooks/exhaustive-deps
14
- deps
15
- );
16
- useEffect(() => {
11
+ });
17
12
  scheduler.attach();
18
13
  scheduler.execute();
19
14
  return () => {
20
15
  scheduler.detach();
21
- cancelAnimationFrame(raf.current);
16
+ cancelFn?.();
22
17
  };
23
- }, [scheduler]);
18
+ }, deps);
24
19
  }
25
20
  export {
26
21
  useReactor
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useReactor.ts"],
4
- "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { useEffect, useMemo, useRef } from 'react'\n\n/** @public */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tconst raf = useRef(-1)\n\tconst scheduler = useMemo(\n\t\t() =>\n\t\t\tnew EffectScheduler(name, reactFn, {\n\t\t\t\tscheduleEffect: (cb) => {\n\t\t\t\t\tconst rafId = requestAnimationFrame(cb)\n\t\t\t\t\traf.current = rafId\n\t\t\t\t\treturn rafId\n\t\t\t\t},\n\t\t\t}),\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\tdeps\n\t)\n\n\tuseEffect(() => {\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelAnimationFrame(raf.current)\n\t\t}\n\t}, [scheduler])\n}\n"],
5
- "mappings": "AAAA,SAAS,uBAAuB;AAChC,SAAS,WAAW,SAAS,cAAc;AAGpC,SAAS,WAAW,MAAc,SAAqB,OAA0B,CAAC,GAAG;AAC3F,QAAM,MAAM,OAAO,EAAE;AACrB,QAAM,YAAY;AAAA,IACjB,MACC,IAAI,gBAAgB,MAAM,SAAS;AAAA,MAClC,gBAAgB,CAAC,OAAO;AACvB,cAAM,QAAQ,sBAAsB,EAAE;AACtC,YAAI,UAAU;AACd,eAAO;AAAA,MACR;AAAA,IACD,CAAC;AAAA;AAAA,IAEF;AAAA,EACD;AAEA,YAAU,MAAM;AACf,cAAU,OAAO;AACjB,cAAU,QAAQ;AAClB,WAAO,MAAM;AACZ,gBAAU,OAAO;AACjB,2BAAqB,IAAI,OAAO;AAAA,IACjC;AAAA,EACD,GAAG,CAAC,SAAS,CAAC;AACf;",
4
+ "sourcesContent": ["import { EffectScheduler } from '@tldraw/state'\nimport { throttleToNextFrame } from '@tldraw/utils'\nimport { useEffect } from 'react'\n\n/** @public */\nexport function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {\n\tuseEffect(() => {\n\t\tlet cancelFn: () => void | undefined\n\t\tconst scheduler = new EffectScheduler(name, reactFn, {\n\t\t\tscheduleEffect: (cb) => {\n\t\t\t\tcancelFn = throttleToNextFrame(cb)\n\t\t\t},\n\t\t})\n\t\tscheduler.attach()\n\t\tscheduler.execute()\n\t\treturn () => {\n\t\t\tscheduler.detach()\n\t\t\tcancelFn?.()\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n}\n"],
5
+ "mappings": "AAAA,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAGnB,SAAS,WAAW,MAAc,SAAqB,OAA0B,CAAC,GAAG;AAC3F,YAAU,MAAM;AACf,QAAI;AACJ,UAAM,YAAY,IAAI,gBAAgB,MAAM,SAAS;AAAA,MACpD,gBAAgB,CAAC,OAAO;AACvB,mBAAW,oBAAoB,EAAE;AAAA,MAClC;AAAA,IACD,CAAC;AACD,cAAU,OAAO;AACjB,cAAU,QAAQ;AAClB,WAAO,MAAM;AACZ,gBAAU,OAAO;AACjB,iBAAW;AAAA,IACZ;AAAA,EAED,GAAG,IAAI;AACR;",
6
6
  "names": []
7
7
  }
@@ -1,43 +1,27 @@
1
1
  import { computed, react } from "@tldraw/state";
2
- import { useMemo, useRef, useSyncExternalStore } from "react";
2
+ import { useMemo, useSyncExternalStore } from "react";
3
3
  function useValue() {
4
4
  const args = arguments;
5
5
  const deps = args.length === 3 ? args[2] : [args[0]];
6
6
  const name = args.length === 3 ? args[0] : `useValue(${args[0].name})`;
7
- const isInRender = useRef(true);
8
- isInRender.current = true;
9
- const $val = useMemo(() => {
10
- if (args.length === 1) {
11
- return args[0];
12
- }
13
- return computed(name, () => {
14
- if (isInRender.current) {
15
- return args[1]();
16
- } else {
17
- try {
18
- return args[1]();
19
- } catch {
20
- return {};
21
- }
22
- }
23
- });
7
+ const { $val, subscribe, getSnapshot } = useMemo(() => {
8
+ const $val2 = args.length === 1 ? args[0] : computed(name, args[1]);
9
+ return {
10
+ $val: $val2,
11
+ subscribe: (notify) => {
12
+ return react(`useValue(${name})`, () => {
13
+ try {
14
+ $val2.get();
15
+ } catch {
16
+ }
17
+ notify();
18
+ });
19
+ },
20
+ getSnapshot: () => $val2.lastChangedEpoch
21
+ };
24
22
  }, deps);
25
- try {
26
- const { subscribe, getSnapshot } = useMemo(() => {
27
- return {
28
- subscribe: (listen) => {
29
- return react(`useValue(${name})`, () => {
30
- $val.get();
31
- listen();
32
- });
33
- },
34
- getSnapshot: () => $val.get()
35
- };
36
- }, [$val]);
37
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
38
- } finally {
39
- isInRender.current = false;
40
- }
23
+ useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
24
+ return $val.__unsafe__getWithoutCapture();
41
25
  }
42
26
  export {
43
27
  useValue
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/useValue.ts"],
4
- "sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Signal, computed, react } from '@tldraw/state'\nimport { useMemo, useRef, useSyncExternalStore } from 'react'\n\n/** @public */\nexport function useValue<Value>(value: Signal<Value>): Value\n\n/**\n * Extracts the value from a signal and subscribes to it.\n *\n * Note that you do not need to use this hook if you are wrapping the component with {@link track}\n *\n * @example\n * ```ts\n * const Counter: React.FC = () => {\n * const $count = useAtom('count', 0)\n * const increment = useCallback(() => $count.set($count.get() + 1), [count])\n * const currentCount = useValue($count)\n * return <button onClick={increment}>{currentCount}</button>\n * }\n * ```\n *\n * You can also pass a function to compute the value and it will be memoized as in `useComputed`:\n *\n * @example\n * ```ts\n * type GreeterProps = {\n * firstName: Signal<string>\n * lastName: Signal<string>\n * }\n *\n * const Greeter = track(function Greeter({ firstName, lastName }: GreeterProps) {\n * const fullName = useValue('fullName', () => `${firstName.get()} ${lastName.get()}`, [\n * firstName,\n * lastName,\n * ])\n * return <div>Hello {fullName}!</div>\n * })\n * ```\n *\n * @public\n */\nexport function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value\n\n/** @public */\nexport function useValue() {\n\tconst args = arguments\n\t// deps will be either the computed or the deps array\n\tconst deps = args.length === 3 ? args[2] : [args[0]]\n\tconst name = args.length === 3 ? args[0] : `useValue(${args[0].name})`\n\n\tconst isInRender = useRef(true)\n\tisInRender.current = true\n\n\tconst $val = useMemo(() => {\n\t\tif (args.length === 1) {\n\t\t\treturn args[0]\n\t\t}\n\t\treturn computed(name, () => {\n\t\t\tif (isInRender.current) {\n\t\t\t\treturn args[1]()\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\treturn args[1]()\n\t\t\t\t} catch {\n\t\t\t\t\t// when getSnapshot is called outside of the render phase &\n\t\t\t\t\t// subsequently throws an error, it might be because we're\n\t\t\t\t\t// in a zombie-child state. in that case, we suppress the\n\t\t\t\t\t// error and instead return a new dummy value to trigger a\n\t\t\t\t\t// react re-render. if we were in a zombie child, react will\n\t\t\t\t\t// unmount us instead of re-rendering so the error is\n\t\t\t\t\t// irrelevant. if we're not in a zombie-child, react will\n\t\t\t\t\t// call `getSnapshot` again in the render phase, and the\n\t\t\t\t\t// error will be thrown as expected.\n\t\t\t\t\treturn {}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n\n\ttry {\n\t\tconst { subscribe, getSnapshot } = useMemo(() => {\n\t\t\treturn {\n\t\t\t\tsubscribe: (listen: () => void) => {\n\t\t\t\t\treturn react(`useValue(${name})`, () => {\n\t\t\t\t\t\t$val.get()\n\t\t\t\t\t\tlisten()\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tgetSnapshot: () => $val.get(),\n\t\t\t}\n\t\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t}, [$val])\n\n\t\treturn useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\t} finally {\n\t\tisInRender.current = false\n\t}\n}\n"],
5
- "mappings": "AACA,SAAiB,UAAU,aAAa;AACxC,SAAS,SAAS,QAAQ,4BAA4B;AA2C/C,SAAS,WAAW;AAC1B,QAAM,OAAO;AAEb,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnD,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,EAAE,IAAI;AAEnE,QAAM,aAAa,OAAO,IAAI;AAC9B,aAAW,UAAU;AAErB,QAAM,OAAO,QAAQ,MAAM;AAC1B,QAAI,KAAK,WAAW,GAAG;AACtB,aAAO,KAAK,CAAC;AAAA,IACd;AACA,WAAO,SAAS,MAAM,MAAM;AAC3B,UAAI,WAAW,SAAS;AACvB,eAAO,KAAK,CAAC,EAAE;AAAA,MAChB,OAAO;AACN,YAAI;AACH,iBAAO,KAAK,CAAC,EAAE;AAAA,QAChB,QAAQ;AAUP,iBAAO,CAAC;AAAA,QACT;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EAEF,GAAG,IAAI;AAEP,MAAI;AACH,UAAM,EAAE,WAAW,YAAY,IAAI,QAAQ,MAAM;AAChD,aAAO;AAAA,QACN,WAAW,CAAC,WAAuB;AAClC,iBAAO,MAAM,YAAY,IAAI,KAAK,MAAM;AACvC,iBAAK,IAAI;AACT,mBAAO;AAAA,UACR,CAAC;AAAA,QACF;AAAA,QACA,aAAa,MAAM,KAAK,IAAI;AAAA,MAC7B;AAAA,IAED,GAAG,CAAC,IAAI,CAAC;AAET,WAAO,qBAAqB,WAAW,aAAa,WAAW;AAAA,EAChE,UAAE;AACD,eAAW,UAAU;AAAA,EACtB;AACD;",
6
- "names": []
4
+ "sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { Signal, computed, react } from '@tldraw/state'\nimport { useMemo, useSyncExternalStore } from 'react'\n\n/** @public */\nexport function useValue<Value>(value: Signal<Value>): Value\n\n/**\n * Extracts the value from a signal and subscribes to it.\n *\n * Note that you do not need to use this hook if you are wrapping the component with {@link track}\n *\n * @example\n * ```ts\n * const Counter: React.FC = () => {\n * const $count = useAtom('count', 0)\n * const increment = useCallback(() => $count.set($count.get() + 1), [count])\n * const currentCount = useValue($count)\n * return <button onClick={increment}>{currentCount}</button>\n * }\n * ```\n *\n * You can also pass a function to compute the value and it will be memoized as in `useComputed`:\n *\n * @example\n * ```ts\n * type GreeterProps = {\n * firstName: Signal<string>\n * lastName: Signal<string>\n * }\n *\n * const Greeter = track(function Greeter({ firstName, lastName }: GreeterProps) {\n * const fullName = useValue('fullName', () => `${firstName.get()} ${lastName.get()}`, [\n * firstName,\n * lastName,\n * ])\n * return <div>Hello {fullName}!</div>\n * })\n * ```\n *\n * @public\n */\nexport function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value\n\n/** @public */\nexport function useValue() {\n\tconst args = arguments\n\t// deps will be either the computed or the deps array\n\tconst deps = args.length === 3 ? args[2] : [args[0]]\n\tconst name = args.length === 3 ? args[0] : `useValue(${args[0].name})`\n\n\tconst { $val, subscribe, getSnapshot } = useMemo(() => {\n\t\tconst $val =\n\t\t\targs.length === 1 ? (args[0] as Signal<any>) : (computed(name, args[1]) as Signal<any>)\n\n\t\treturn {\n\t\t\t$val,\n\t\t\tsubscribe: (notify: () => void) => {\n\t\t\t\treturn react(`useValue(${name})`, () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t$val.get()\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Will be rethrown during render if the component doesn't unmount first.\n\t\t\t\t\t}\n\t\t\t\t\tnotify()\n\t\t\t\t})\n\t\t\t},\n\t\t\tgetSnapshot: () => $val.lastChangedEpoch,\n\t\t}\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, deps)\n\n\tuseSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\treturn $val.__unsafe__getWithoutCapture()\n}\n"],
5
+ "mappings": "AACA,SAAiB,UAAU,aAAa;AACxC,SAAS,SAAS,4BAA4B;AA2CvC,SAAS,WAAW;AAC1B,QAAM,OAAO;AAEb,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnD,QAAM,OAAO,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,EAAE,IAAI;AAEnE,QAAM,EAAE,MAAM,WAAW,YAAY,IAAI,QAAQ,MAAM;AACtD,UAAMA,QACL,KAAK,WAAW,IAAK,KAAK,CAAC,IAAqB,SAAS,MAAM,KAAK,CAAC,CAAC;AAEvE,WAAO;AAAA,MACN,MAAAA;AAAA,MACA,WAAW,CAAC,WAAuB;AAClC,eAAO,MAAM,YAAY,IAAI,KAAK,MAAM;AACvC,cAAI;AACH,YAAAA,MAAK,IAAI;AAAA,UACV,QAAQ;AAAA,UAER;AACA,iBAAO;AAAA,QACR,CAAC;AAAA,MACF;AAAA,MACA,aAAa,MAAMA,MAAK;AAAA,IACzB;AAAA,EAED,GAAG,IAAI;AAEP,uBAAqB,WAAW,aAAa,WAAW;AACxD,SAAO,KAAK,4BAA4B;AACzC;",
6
+ "names": ["$val"]
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/state-react",
3
- "description": "A tiny little drawing app (react bindings for state).",
4
- "version": "3.16.0-internal.a478398270c6",
3
+ "description": "tldraw infinite canvas SDK (react bindings for state).",
4
+ "version": "3.16.0-internal.f8b97f0c414f",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "keywords": [
19
19
  "tldraw",
20
+ "sdk",
20
21
  "drawing",
21
22
  "app",
22
23
  "development",
@@ -31,38 +32,29 @@
31
32
  "src"
32
33
  ],
33
34
  "scripts": {
34
- "test-ci": "lazy inherit",
35
- "test": "yarn run -T jest",
36
- "test-coverage": "lazy inherit",
35
+ "test-ci": "yarn run -T vitest run --passWithNoTests",
36
+ "test": "yarn run -T vitest --passWithNoTests",
37
+ "test-coverage": "yarn run -T vitest run --coverage --passWithNoTests",
37
38
  "build": "yarn run -T tsx ../../internal/scripts/build-package.ts",
38
39
  "build-api": "yarn run -T tsx ../../internal/scripts/build-api.ts",
39
40
  "prepack": "yarn run -T tsx ../../internal/scripts/prepack.ts",
40
41
  "postpack": "../../internal/scripts/postpack.sh",
41
42
  "pack-tarball": "yarn pack",
42
- "lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
43
- },
44
- "jest": {
45
- "preset": "../../internal/config/jest/node/jest-preset.js",
46
- "setupFiles": [
47
- "raf/polyfill"
48
- ],
49
- "moduleNameMapper": {
50
- "^~(.*)": "<rootDir>/src/$1"
51
- },
52
- "testEnvironment": "jsdom"
43
+ "lint": "yarn run -T tsx ../../internal/scripts/lint.ts",
44
+ "context": "yarn run -T tsx ../../internal/scripts/context.ts"
53
45
  },
54
46
  "dependencies": {
55
- "@tldraw/state": "3.16.0-internal.a478398270c6",
56
- "@tldraw/utils": "3.16.0-internal.a478398270c6"
47
+ "@tldraw/state": "3.16.0-internal.f8b97f0c414f",
48
+ "@tldraw/utils": "3.16.0-internal.f8b97f0c414f"
57
49
  },
58
50
  "devDependencies": {
59
- "@testing-library/jest-dom": "^5.17.0",
60
51
  "@testing-library/react": "^15.0.7",
61
52
  "@types/lodash": "^4.17.14",
62
53
  "@types/react": "^18.3.18",
63
54
  "lodash": "^4.17.21",
64
55
  "react": "^18.3.1",
65
- "react-dom": "^18.3.1"
56
+ "react-dom": "^18.3.1",
57
+ "vitest": "^3.2.4"
66
58
  },
67
59
  "peerDependencies": {
68
60
  "react": "^18.2.0 || ^19.0.0",
@@ -1,6 +1,7 @@
1
1
  import { act, render, RenderResult } from '@testing-library/react'
2
2
  import { Atom, Computed } from '@tldraw/state'
3
3
  import { useState } from 'react'
4
+ import { vi } from 'vitest'
4
5
  import { useAtom } from './useAtom'
5
6
  import { useComputed } from './useComputed'
6
7
  import { useValue } from './useValue'
@@ -76,7 +77,7 @@ test('useComputed allows optionally passing options', async () => {
76
77
  let theComputed = null as null | Computed<number>
77
78
  let theAtom = null as null | Atom<number>
78
79
  let setCount = null as null | ((count: number) => void)
79
- const isEqual = jest.fn((a, b) => a === b)
80
+ const isEqual = vi.fn((a, b) => a === b)
80
81
  function Component() {
81
82
  const [count, _setCount] = useState(0)
82
83
  setCount = _setCount
@@ -1,28 +1,22 @@
1
1
  import { EffectScheduler } from '@tldraw/state'
2
- import { useEffect, useMemo, useRef } from 'react'
2
+ import { throttleToNextFrame } from '@tldraw/utils'
3
+ import { useEffect } from 'react'
3
4
 
4
5
  /** @public */
5
6
  export function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {
6
- const raf = useRef(-1)
7
- const scheduler = useMemo(
8
- () =>
9
- new EffectScheduler(name, reactFn, {
10
- scheduleEffect: (cb) => {
11
- const rafId = requestAnimationFrame(cb)
12
- raf.current = rafId
13
- return rafId
14
- },
15
- }),
16
- // eslint-disable-next-line react-hooks/exhaustive-deps
17
- deps
18
- )
19
-
20
7
  useEffect(() => {
8
+ let cancelFn: () => void | undefined
9
+ const scheduler = new EffectScheduler(name, reactFn, {
10
+ scheduleEffect: (cb) => {
11
+ cancelFn = throttleToNextFrame(cb)
12
+ },
13
+ })
21
14
  scheduler.attach()
22
15
  scheduler.execute()
23
16
  return () => {
24
17
  scheduler.detach()
25
- cancelAnimationFrame(raf.current)
18
+ cancelFn?.()
26
19
  }
27
- }, [scheduler])
20
+ // eslint-disable-next-line react-hooks/exhaustive-deps
21
+ }, deps)
28
22
  }
@@ -1,10 +1,37 @@
1
1
  import { RenderResult, act, render } from '@testing-library/react'
2
2
  import { Atom, Computed, atom } from '@tldraw/state'
3
- import { useState } from 'react'
3
+ import { Component, ReactNode, useState } from 'react'
4
+ import { vi } from 'vitest'
4
5
  import { useAtom } from './useAtom'
5
6
  import { useComputed } from './useComputed'
6
7
  import { useValue } from './useValue'
7
8
 
9
+ // Error boundary component for testing
10
+ class TestErrorBoundary extends Component<
11
+ { children: ReactNode; onError?(error: Error): void },
12
+ { hasError: boolean; error: Error | null }
13
+ > {
14
+ constructor(props: { children: ReactNode; onError?(error: Error): void }) {
15
+ super(props)
16
+ this.state = { hasError: false, error: null }
17
+ }
18
+
19
+ static getDerivedStateFromError(error: Error) {
20
+ return { hasError: true, error }
21
+ }
22
+
23
+ override componentDidCatch(error: Error) {
24
+ this.props.onError?.(error)
25
+ }
26
+
27
+ override render() {
28
+ if (this.state.hasError) {
29
+ return <div data-testid="error-boundary">Error: {this.state.error?.message}</div>
30
+ }
31
+ return this.props.children
32
+ }
33
+ }
34
+
8
35
  test('useValue returns a value from a computed', async () => {
9
36
  let theComputed = null as null | Computed<number>
10
37
  let theAtom = null as null | Atom<number>
@@ -85,6 +112,7 @@ test('useValue returns a value from a compute function', async () => {
85
112
 
86
113
  test("useValue doesn't throw when used in a zombie-child component", async () => {
87
114
  const theAtom = atom<Record<string, number>>('map', { a: 1, b: 2, c: 3 })
115
+ let numThrows = 0
88
116
  function Parent() {
89
117
  const ids = useValue('ids', () => Object.keys(theAtom.get()), [])
90
118
  return (
@@ -99,7 +127,10 @@ test("useValue doesn't throw when used in a zombie-child component", async () =>
99
127
  const value = useValue(
100
128
  'value',
101
129
  () => {
102
- if (!(id in theAtom.get())) throw new Error('id not found!')
130
+ if (!(id in theAtom.get())) {
131
+ numThrows++
132
+ throw new Error('id not found!')
133
+ }
103
134
  return theAtom.get()[id]
104
135
  },
105
136
  [id]
@@ -108,16 +139,71 @@ test("useValue doesn't throw when used in a zombie-child component", async () =>
108
139
  }
109
140
 
110
141
  let view: RenderResult
111
- await act(() => {
142
+ act(() => {
112
143
  view = render(<Parent />)
113
144
  })
114
145
 
115
146
  expect(view!.asFragment().textContent).toMatchInlineSnapshot('"123"')
116
147
 
148
+ expect(numThrows).toBe(0)
117
149
  // remove id 'b' creating a zombie-child
118
- await act(() => {
150
+ act(() => {
119
151
  theAtom?.update(({ b: _, ...rest }) => rest)
120
152
  })
121
153
 
122
154
  expect(view!.asFragment().textContent).toMatchInlineSnapshot('"13"')
155
+
156
+ expect(numThrows).toBe(1)
157
+ })
158
+
159
+ test('useValue throws synchronously during render when the computed throws', async () => {
160
+ const theAtom = atom<Error | null>('map', null)
161
+ let caughtError = null as null | Error
162
+
163
+ // Suppress React's console.error for this test
164
+
165
+ function Component({ id }: { id: string }) {
166
+ const value = useValue(
167
+ 'value',
168
+ () => {
169
+ const error = theAtom.get()
170
+ if (error) throw error
171
+ return 1
172
+ },
173
+ [id]
174
+ )
175
+ return <>{value}</>
176
+ }
177
+
178
+ let view: RenderResult
179
+ act(() => {
180
+ view = render(
181
+ <TestErrorBoundary
182
+ onError={(error) => {
183
+ caughtError = error
184
+ }}
185
+ >
186
+ <Component id="a" />
187
+ </TestErrorBoundary>
188
+ )
189
+ })
190
+
191
+ expect(view!.asFragment().textContent).toMatchInlineSnapshot('"1"')
192
+
193
+ // ignore console.error here because react will log the error to console.error
194
+ // even though it's caught by the error boundary
195
+ const originalError = console.error
196
+ console.error = vi.fn()
197
+ try {
198
+ act(() => {
199
+ theAtom.set(new Error('test'))
200
+ })
201
+ } finally {
202
+ console.error = originalError
203
+ }
204
+
205
+ expect(caughtError).toBeInstanceOf(Error)
206
+ expect(caughtError?.message).toBe('test')
207
+ expect(view!.getByTestId('error-boundary')).toBeTruthy()
208
+ expect(view!.getByTestId('error-boundary').textContent).toBe('Error: test')
123
209
  })
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable prefer-rest-params */
2
2
  import { Signal, computed, react } from '@tldraw/state'
3
- import { useMemo, useRef, useSyncExternalStore } from 'react'
3
+ import { useMemo, useSyncExternalStore } from 'react'
4
4
 
5
5
  /** @public */
6
6
  export function useValue<Value>(value: Signal<Value>): Value
@@ -49,52 +49,27 @@ export function useValue() {
49
49
  const deps = args.length === 3 ? args[2] : [args[0]]
50
50
  const name = args.length === 3 ? args[0] : `useValue(${args[0].name})`
51
51
 
52
- const isInRender = useRef(true)
53
- isInRender.current = true
52
+ const { $val, subscribe, getSnapshot } = useMemo(() => {
53
+ const $val =
54
+ args.length === 1 ? (args[0] as Signal<any>) : (computed(name, args[1]) as Signal<any>)
54
55
 
55
- const $val = useMemo(() => {
56
- if (args.length === 1) {
57
- return args[0]
56
+ return {
57
+ $val,
58
+ subscribe: (notify: () => void) => {
59
+ return react(`useValue(${name})`, () => {
60
+ try {
61
+ $val.get()
62
+ } catch {
63
+ // Will be rethrown during render if the component doesn't unmount first.
64
+ }
65
+ notify()
66
+ })
67
+ },
68
+ getSnapshot: () => $val.lastChangedEpoch,
58
69
  }
59
- return computed(name, () => {
60
- if (isInRender.current) {
61
- return args[1]()
62
- } else {
63
- try {
64
- return args[1]()
65
- } catch {
66
- // when getSnapshot is called outside of the render phase &
67
- // subsequently throws an error, it might be because we're
68
- // in a zombie-child state. in that case, we suppress the
69
- // error and instead return a new dummy value to trigger a
70
- // react re-render. if we were in a zombie child, react will
71
- // unmount us instead of re-rendering so the error is
72
- // irrelevant. if we're not in a zombie-child, react will
73
- // call `getSnapshot` again in the render phase, and the
74
- // error will be thrown as expected.
75
- return {}
76
- }
77
- }
78
- })
79
70
  // eslint-disable-next-line react-hooks/exhaustive-deps
80
71
  }, deps)
81
72
 
82
- try {
83
- const { subscribe, getSnapshot } = useMemo(() => {
84
- return {
85
- subscribe: (listen: () => void) => {
86
- return react(`useValue(${name})`, () => {
87
- $val.get()
88
- listen()
89
- })
90
- },
91
- getSnapshot: () => $val.get(),
92
- }
93
- // eslint-disable-next-line react-hooks/exhaustive-deps
94
- }, [$val])
95
-
96
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
97
- } finally {
98
- isInRender.current = false
99
- }
73
+ useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
74
+ return $val.__unsafe__getWithoutCapture()
100
75
  }