@tldraw/state-react 4.1.0-next.1b89b40eff1c → 4.1.0-next.9f145d10c7d0
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.d.ts +241 -35
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/track.js.map +2 -2
- package/dist-cjs/lib/useComputed.js.map +2 -2
- package/dist-cjs/lib/useQuickReactor.js.map +2 -2
- package/dist-cjs/lib/useReactor.js.map +2 -2
- package/dist-cjs/lib/useStateTracking.js +1 -1
- package/dist-cjs/lib/useStateTracking.js.map +2 -2
- package/dist-cjs/lib/useValue.js.map +2 -2
- package/dist-esm/index.d.mts +241 -35
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/track.mjs.map +2 -2
- package/dist-esm/lib/useComputed.mjs.map +2 -2
- package/dist-esm/lib/useQuickReactor.mjs.map +2 -2
- package/dist-esm/lib/useReactor.mjs.map +2 -2
- package/dist-esm/lib/useStateTracking.mjs.map +2 -2
- package/dist-esm/lib/useValue.mjs.map +2 -2
- package/package.json +3 -3
- package/src/lib/track.test.tsx +1 -1
- package/src/lib/track.ts +65 -2
- package/src/lib/useComputed.ts +62 -11
- package/src/lib/useQuickReactor.test.tsx +405 -0
- package/src/lib/useQuickReactor.ts +40 -1
- package/src/lib/useReactor.test.tsx +128 -0
- package/src/lib/useReactor.ts +72 -1
- package/src/lib/useStateTracking.ts +5 -0
- package/src/lib/useValue.ts +55 -22
package/src/lib/useReactor.ts
CHANGED
|
@@ -2,7 +2,78 @@ import { EffectScheduler } from '@tldraw/state'
|
|
|
2
2
|
import { throttleToNextFrame } from '@tldraw/utils'
|
|
3
3
|
import { useEffect } from 'react'
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* A React hook that runs a side effect in response to changes in signals (reactive state).
|
|
7
|
+
*
|
|
8
|
+
* The effect function will automatically track any signals (atoms, computed values) that are
|
|
9
|
+
* accessed during its execution. When any of those signals change, the effect will be
|
|
10
|
+
* scheduled to run again on the next animation frame.
|
|
11
|
+
*
|
|
12
|
+
* This is useful for performing side effects (like updating the DOM, making API calls, or
|
|
13
|
+
* updating external state) in response to changes in tldraw's reactive state system, while
|
|
14
|
+
* keeping those effects efficiently batched and throttled.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* import { useReactor, useEditor } from 'tldraw'
|
|
19
|
+
*
|
|
20
|
+
* function MyComponent() {
|
|
21
|
+
* const editor = useEditor()
|
|
22
|
+
*
|
|
23
|
+
* // Update document title when shapes change
|
|
24
|
+
* useReactor(
|
|
25
|
+
* 'update title',
|
|
26
|
+
* () => {
|
|
27
|
+
* const shapes = editor.getCurrentPageShapes()
|
|
28
|
+
* document.title = `Shapes: ${shapes.length}`
|
|
29
|
+
* },
|
|
30
|
+
* [editor]
|
|
31
|
+
* )
|
|
32
|
+
*
|
|
33
|
+
* return <div>...</div>
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import { useReactor, useEditor } from 'tldraw'
|
|
40
|
+
*
|
|
41
|
+
* function SelectionAnnouncer() {
|
|
42
|
+
* const editor = useEditor()
|
|
43
|
+
*
|
|
44
|
+
* // Announce selection changes for accessibility
|
|
45
|
+
* useReactor(
|
|
46
|
+
* 'announce selection',
|
|
47
|
+
* () => {
|
|
48
|
+
* const selectedIds = editor.getSelectedShapeIds()
|
|
49
|
+
* if (selectedIds.length > 0) {
|
|
50
|
+
* console.log(`Selected ${selectedIds.length} shape(s)`)
|
|
51
|
+
* }
|
|
52
|
+
* },
|
|
53
|
+
* [editor]
|
|
54
|
+
* )
|
|
55
|
+
*
|
|
56
|
+
* return null
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* The effect is throttled to run at most once per animation frame using `requestAnimationFrame`.
|
|
62
|
+
* This makes it suitable for effects that need to respond to state changes but don't need to
|
|
63
|
+
* run synchronously.
|
|
64
|
+
*
|
|
65
|
+
* If you need the effect to run immediately without throttling, use {@link useQuickReactor} instead.
|
|
66
|
+
*
|
|
67
|
+
* The effect function will be re-created when any of the `deps` change, similar to React's
|
|
68
|
+
* `useEffect`. The effect automatically tracks which signals it accesses, so you don't need
|
|
69
|
+
* to manually specify them as dependencies.
|
|
70
|
+
*
|
|
71
|
+
* @param name - A debug name for the effect, useful for debugging and performance profiling.
|
|
72
|
+
* @param reactFn - The effect function to run. Any signals accessed in this function will be tracked.
|
|
73
|
+
* @param deps - React dependencies array. The effect will be recreated when these change. Defaults to `[]`.
|
|
74
|
+
*
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
6
77
|
export function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {
|
|
7
78
|
useEffect(() => {
|
|
8
79
|
let cancelFn: () => void | undefined
|
|
@@ -8,6 +8,11 @@ import React from 'react'
|
|
|
8
8
|
*
|
|
9
9
|
* See the `track` component wrapper, which uses this under the hood.
|
|
10
10
|
*
|
|
11
|
+
* @param name - A debug name for the reactive tracking context
|
|
12
|
+
* @param render - The render function that accesses reactive values
|
|
13
|
+
* @param deps - Optional dependency array to control when the tracking context is recreated
|
|
14
|
+
* @returns The result of calling the render function
|
|
15
|
+
*
|
|
11
16
|
* @example
|
|
12
17
|
* ```ts
|
|
13
18
|
* function MyComponent() {
|
package/src/lib/useValue.ts
CHANGED
|
@@ -2,47 +2,80 @@
|
|
|
2
2
|
import { Signal, computed, react } from '@tldraw/state'
|
|
3
3
|
import { useMemo, useSyncExternalStore } from 'react'
|
|
4
4
|
|
|
5
|
-
/** @public */
|
|
6
|
-
export function useValue<Value>(value: Signal<Value>): Value
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
|
-
* Extracts the value from a signal and subscribes to
|
|
6
|
+
* Extracts the current value from a signal and subscribes the component to changes.
|
|
7
|
+
*
|
|
8
|
+
* This is the most straightforward way to read signal values in React components.
|
|
9
|
+
* When the signal changes, the component will automatically re-render with the new value.
|
|
10
10
|
*
|
|
11
|
-
* Note
|
|
11
|
+
* Note: You do not need to use this hook if you are wrapping the component with {@link track},
|
|
12
|
+
* as tracked components automatically subscribe to any signals accessed with `.get()`.
|
|
13
|
+
*
|
|
14
|
+
* @param value - The signal to read the value from
|
|
15
|
+
* @returns The current value of the signal
|
|
12
16
|
*
|
|
13
17
|
* @example
|
|
14
18
|
* ```ts
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* import { atom } from '@tldraw/state'
|
|
20
|
+
* import { useValue } from '@tldraw/state-react'
|
|
21
|
+
*
|
|
22
|
+
* const count = atom('count', 0)
|
|
23
|
+
*
|
|
24
|
+
* function Counter() {
|
|
25
|
+
* const currentCount = useValue(count)
|
|
26
|
+
* return (
|
|
27
|
+
* <button onClick={() => count.set(currentCount + 1)}>
|
|
28
|
+
* Count: {currentCount}
|
|
29
|
+
* </button>
|
|
30
|
+
* )
|
|
20
31
|
* }
|
|
21
32
|
* ```
|
|
22
33
|
*
|
|
23
|
-
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export function useValue<Value>(value: Signal<Value>): Value
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a computed value with automatic dependency tracking and subscribes to changes.
|
|
40
|
+
*
|
|
41
|
+
* This overload allows you to compute a value from one or more signals with automatic
|
|
42
|
+
* memoization. The computed function will only re-execute when its dependencies change,
|
|
43
|
+
* and the component will only re-render when the computed result changes.
|
|
44
|
+
*
|
|
45
|
+
* @param name - A descriptive name for debugging purposes
|
|
46
|
+
* @param fn - Function that computes the value, should call `.get()` on any signals it depends on
|
|
47
|
+
* @param deps - Array of signals that the computed function depends on
|
|
48
|
+
* @returns The computed value
|
|
24
49
|
*
|
|
25
50
|
* @example
|
|
26
51
|
* ```ts
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
52
|
+
* import { atom } from '@tldraw/state'
|
|
53
|
+
* import { useValue } from '@tldraw/state-react'
|
|
54
|
+
*
|
|
55
|
+
* const firstName = atom('firstName', 'John')
|
|
56
|
+
* const lastName = atom('lastName', 'Doe')
|
|
57
|
+
*
|
|
58
|
+
* function UserGreeting() {
|
|
59
|
+
* const fullName = useValue('fullName', () => {
|
|
60
|
+
* return `${firstName.get()} ${lastName.get()}`
|
|
61
|
+
* }, [firstName, lastName])
|
|
31
62
|
*
|
|
32
|
-
* const Greeter = track(function Greeter({ firstName, lastName }: GreeterProps) {
|
|
33
|
-
* const fullName = useValue('fullName', () => `${firstName.get()} ${lastName.get()}`, [
|
|
34
|
-
* firstName,
|
|
35
|
-
* lastName,
|
|
36
|
-
* ])
|
|
37
63
|
* return <div>Hello {fullName}!</div>
|
|
38
|
-
* }
|
|
64
|
+
* }
|
|
39
65
|
* ```
|
|
40
66
|
*
|
|
41
67
|
* @public
|
|
42
68
|
*/
|
|
43
69
|
export function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value
|
|
44
70
|
|
|
45
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Implementation function for useValue hook overloads.
|
|
73
|
+
*
|
|
74
|
+
* Handles both single signal subscription and computed value creation with dependency tracking.
|
|
75
|
+
* Uses React's useSyncExternalStore for efficient subscription management and automatic cleanup.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
46
79
|
export function useValue() {
|
|
47
80
|
const args = arguments
|
|
48
81
|
// deps will be either the computed or the deps array
|