@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.
@@ -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
- /** @public */
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() {
@@ -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 it.
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 that you do not need to use this hook if you are wrapping the component with {@link track}
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
- * const Counter: React.FC = () => {
16
- * const $count = useAtom('count', 0)
17
- * const increment = useCallback(() => $count.set($count.get() + 1), [count])
18
- * const currentCount = useValue($count)
19
- * return <button onClick={increment}>{currentCount}</button>
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
- * You can also pass a function to compute the value and it will be memoized as in `useComputed`:
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
- * type GreeterProps = {
28
- * firstName: Signal<string>
29
- * lastName: Signal<string>
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
- /** @public */
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