You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`value`| The value to unwrap - a proxy or any other value. |
28
28
29
29
### Returns
@@ -32,7 +32,7 @@ The raw state object if `value` is a tracking proxy, otherwise `value` itself.
32
32
33
33
## Usage Guide
34
34
35
-
Inside a `useSignalSelector` selector, nested objects read from `state` are tracking proxies. A proxy is never `===` to the raw object it wraps, so identity comparisons against object references captured *outside* the current selector run always fail. `unwrap` resolves the proxy so the comparison works:
35
+
Inside a `useSignalSelector` selector, nested objects read from `state` are tracking proxies. A proxy is never `===` to the raw object it wraps, so identity comparisons against object references captured _outside_ the current selector run always fail. `unwrap` resolves the proxy so the comparison works:
|`selector`| A function that receives the entire Redux store state as its only argument, and returns the value this component needs from it. |
70
+
|`selector`| A function that receives the entire Redux store state as its only argument, and returns the value this component needs from it. |
71
71
|`options?`| Either an equality function used to compare selector results (such as `shallowEqual`), or an options object containing an `equalityFn` field and a [`devModeChecks`](#development-mode-checks) field for configuring the development-mode checks below. |
|`selector`| A function that receives the entire Redux store state and returns the value this component needs. Must be [pure](./useSelector.md). |
74
74
|`options?`| Either an equality function (such as `shallowEqual`), or an options object with `equalityFn` and `devModeChecks` fields, as in `useSelector`. |
75
75
@@ -98,7 +98,7 @@ This is the same family of technique used by `proxy-memoize` and by fine-grained
98
98
- Your app dispatches frequently, and most dispatches are irrelevant to most components
99
99
- Selectors read narrow, deep paths (`state.entities.todos[id].completed`)
100
100
101
-
It helps least (and can cost slightly more than `useSelector`) when most dispatches change state that most components read anyway, or when selectors read broad swaths of the state. Tracked selector evaluation itself costs roughly 3-4x an untracked run, so the win comes from the runs that are *skipped*, not from the runs themselves.
101
+
It helps least (and can cost slightly more than `useSelector`) when most dispatches change state that most components read anyway, or when selectors read broad swaths of the state. Tracked selector evaluation itself costs roughly 3-4x an untracked run, so the win comes from the runs that are _skipped_, not from the runs themselves.
102
102
103
103
Because `useSelector` and `useSignalSelector` coexist under one `<SignalProvider>`, you can adopt it incrementally in the components with the highest subscription counts.
104
104
@@ -108,20 +108,21 @@ The observable behavior - what your components render, when they re-render, what
108
108
109
109
#### Hard constraints
110
110
111
-
| Constraint | Explanation |
112
-
| :--- | :--- |
113
-
|**Requires `<SignalProvider>`**| Calling `useSignalSelector` under a plain `<Provider>` throws. |
114
-
|**The state root must be a plain object**| Path tracking operates on the root object's keys. If the root is not a plain object, the hook still works, but falls back to untracked evaluation (behaving like `useSelector`). |
115
-
|**`state => state` never updates**| A selector that returns the entire root state records no field reads, so no dispatch will ever re-run it. This is already an anti-pattern (`useSelector`'s [identity function check](./useSelector.md#identity-function-state--state-check) warns about it), but with `useSignalSelector` it goes from "wasteful" to "broken": the component will not update. The dev-mode check catches this. |
116
-
|**Mutations inside `Map`, `Set`, `Date`, and class instances are not tracked**| Only plain objects and arrays get per-field tracking. Other object types are tracked by reference: replacing the instance triggers an update, but reading `myMap.get('key')` does not record a per-entry dependency. Since Redux state should be [immutably updated](https://redux.js.org/style-guide/#do-not-mutate-state) plain data anyway, this mostly matters for state produced by libraries. |
|**Requires `<SignalProvider>`**| Calling `useSignalSelector` under a plain `<Provider>` throws. |
114
+
|**The state root must be a plain object**| Path tracking operates on the root object's keys. If the root is not a plain object, the hook still works, but falls back to untracked evaluation (behaving like `useSelector`). |
115
+
|**`state => state` never updates**| A selector that returns the entire root state records no field reads, so no dispatch will ever re-run it. This is already an anti-pattern (`useSelector`'s [identity function check](./useSelector.md#identity-function-state--state-check) warns about it), but with `useSignalSelector` it goes from "wasteful" to "broken": the component will not update. The dev-mode check catches this. |
116
+
|**Selectors must not mutate state**| Writing to the state from inside a selector (`state.items.sort()`, `state.foo = 1`, `delete state.bar`) throws a `TypeError` in development explaining which property the selector tried to change. `useSelector` silently allows these writes, which corrupt the store; `useSignalSelector` rejects them because the proxy sees every write. Use non-mutating alternatives (`state.items.slice().sort()`, `toSorted()`). |
117
+
|**Mutations inside `Map`, `Set`, `Date`, and class instances are not tracked**| Only plain objects and arrays get per-field tracking. Other object types are tracked by reference: replacing the instance triggers an update, but reading `myMap.get('key')` does not record a per-entry dependency. Since Redux state should be [immutably updated](https://redux.js.org/style-guide/#do-not-mutate-state) plain data anyway, this mostly matters for state produced by libraries. |
117
118
118
119
#### Gotchas
119
120
120
121
These are behaviors that differ from `useSelector` in ways you might notice, but that have straightforward workarounds - or need no workaround at all.
121
122
122
123
##### Proxies and identity
123
124
124
-
Inside the selector, nested objects you read from `state` are tracking proxies, not the raw state objects. The values are identical; the object identity is not. This matters only for `===` comparisons **inside the selector body** against object references obtained *outside* the current selector run:
125
+
Inside the selector, nested objects you read from `state` are tracking proxies, not the raw state objects. The values are identical; the object identity is not. This matters only for `===` comparisons **inside the selector body** against object references obtained _outside_ the current selector run:
See [`unwrap`](./unwrap.md) for details. Comparisons between two values both read from `state` in the same selector run work correctly without unwrapping.
142
143
143
-
**Proxies never escape the selector.** The hook unwraps the selector's return value before handing it to React, so your components, effects, equality functions, and dispatched actions always see plain Redux state. `console.log` of a selector result shows plain data. For the same reason, avoid *storing* a state object read during one selector run in an external variable for use later - the same rule as holding onto an Immer draft. Use `unwrap()` first if you need to do this.
144
+
**Proxies never escape the selector.** The hook unwraps the selector's return value before handing it to React, so your components, effects, equality functions, and dispatched actions always see plain Redux state. `console.log` of a selector result shows plain data. For the same reason, avoid _storing_ a state object read during one selector run in an external variable for use later - the same rule as holding onto an Immer draft. Use `unwrap()` first if you need to do this.
144
145
145
146
##### Memoized (Reselect) selectors
146
147
@@ -152,7 +153,7 @@ Selectors that enumerate the root state's keys (`Object.keys(state)`, spreading
152
153
153
154
##### Aliased state objects
154
155
155
-
If the same object instance is reachable via two different state paths (for example, an entity stored in two lookup tables), tracking attributes reads to the first path encountered. The consequence is only ever *extra* selector re-runs, never missed updates.
156
+
If the same object instance is reachable via two different state paths (for example, an entity stored in two lookup tables), tracking attributes reads to the first path encountered. The consequence is only ever _extra_ selector re-runs, never missed updates.
0 commit comments