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
`useMutableSource()` can also read from non traditional sources, e.g. the shared Location object, so long as they can be subscribed to and have a "version".
Redux users would likely never use the `useMutableSource` hook directly. They would use a hook provided by Redux that uses `useMutableSource` internally.
103
+
104
+
##### Mock Redux implementation
105
+
```js
106
+
// Somewhere, the Redux store needs to be wrapped in a mutable source object...
107
+
constmutableSource=createMutableSource(
108
+
reduxStore,
109
+
// Because the state is immutable, it can be used as the "version".
110
+
() =>reduxStore.getState()
111
+
);
112
+
113
+
// It would probably be shared via the Context API...
@@ -210,7 +248,11 @@ Mutable source requires tracking two pieces of info at the module level:
210
248
211
249
#### Version number
212
250
213
-
Tracking a source's version allows us to avoid tearing during a mount (before our component has subscribed to the source). Whenever a mounting component reads from a mutable source, this number should be checked to ensure that either (1) this is the first mounting component to read from the source during the current render or (2) the version number has not changed since the last read. A changed version number indicates a change in the underlying store data, which may result in a tear.
251
+
Tracking a source's version allows us to avoid tearing when reading from a source that a component has not yet subscribed to.
252
+
253
+
In this case, the version should be checked to ensure that either:
254
+
1. This is the first mounting component to read from the source during the current render, or
255
+
2. The version number has not changed since the last read. (A changed version number indicates a change in the underlying store data, which may result in a tear.)
214
256
215
257
Like Context, this hook should support multiple concurrent renderers (e.g. ReactDOM and ReactART, React Native and React Fabric). To support this, we will track two work-in-progress versions (one for a "primary" renderer and one for a "secondary" renderer).
216
258
@@ -225,7 +267,9 @@ This value should be reset either when a renderer starts a new batch of work or
225
267
226
268
#### Pending update expiration times
227
269
228
-
Tracking pending update times enables already mounted components to safely reuse cached snapshot values without tearing in order to support higher priority updates. During an update, if the current render’s expiration time is **≤** the stored expiration time for a source, it is safe to read new values from the source. Otherwise a cached snapshot value should be used temporarily<sup>1</sup>.
270
+
Tracking pending updates per source enables newly-mounting components to read without potentially conflicting with components that read from the same source during a previous render.
271
+
272
+
During an update, if the current render’s expiration time is **≤** the stored expiration time for a source, it is safe to read new values from the source. Otherwise a cached snapshot value should be used temporarily<sup>1</sup>.
229
273
230
274
When a root is committed, all pending expiration times that are **≤** the committed time can be discarded for that root.
231
275
@@ -253,64 +297,43 @@ Although useful for updates, pending update expiration times are not sufficient
253
297
254
298
The `useMutableSource()` hook’s memoizedState will need to track the following values:
255
299
256
-
- The user-provided config object (with getter functions).
300
+
- The user-provided `getSnapshot` and `subscribe`functions.
257
301
- The latest (cached) snapshot value.
258
302
- The mutable source itself (in order to detect if a new source is provided).
259
-
-A destroy function (to unsubscribe from a source)
303
+
-The (user-returned) unsubscribe function
260
304
261
305
### Scenarios to handle
262
306
263
-
#### Initial mount (before subscription)
307
+
#### Reading from a source before subscribing
264
308
265
-
When a component reads from a mutable source that it has not yet subscribed to<sup>1</sup>, React first checks to see if there are any pending updates for the source already scheduled on the current root.
309
+
When a component reads from a mutable source that it has not yet subscribed to<sup>1</sup>, React first checks the version number to see if anything else has read from this source during the current render.
266
310
267
-
- ✗ If there is a pending update and the current expiration time is **>** the pending time, the read is **not safe**.
268
-
- Throw and restart the render.
269
-
- If there are no pending updates, or if the current expiration time is **≤** the pending time, has the component already subscribed to this source?
270
-
- ✓ If yes, the read is **safe**.
311
+
- If there is a recorded version number (i.e. this is not the first read) does it match the source's current version?
312
+
- ✓ If both versions match, the read is **safe**.
271
313
- Store the snapshot value on `memoizedState`.
272
-
- If no, the the read **may be safe**.
314
+
- ✗ If the version has changed, the read is **not safe**.
315
+
- Throw and restart the render.
273
316
274
-
For components that have not yet subscribed to their source, React reads the version of the source and compares it to the tracked work-in-progress version numbers.
317
+
If there is no version number, the the read **may be safe**. We'll need to next check pending updates for the source to determine this.
275
318
276
-
- ✓ If there is no recorded version, this is the first time the source has been used. The read is **safe**.
277
-
- Record the current version number (on the root) for later reads during mount.
319
+
- ✓ If there are no pending updates the read is **safe**.
278
320
- Store the snapshot value on `memoizedState`.
279
-
- ✓ If the recorded version matches the store version used previously, the read is **safe**.
321
+
- Store the version number for subsequent reads during this render.
322
+
- ✓ If the current expiration time is **≤** the pending time, the read is **safe**.
280
323
- Store the snapshot value on `memoizedState`.
281
-
- ✗ If the recorded version is different, the read is **not safe**.
324
+
- Store the version number for subsequent reads during this render.
325
+
- ✗ If the current expiration time is **>** the pending time, the read is **not safe**.
282
326
- Throw and restart the render.
283
327
284
-
¹ This case could occur during a mount or an update (if a new mutable source was read from for the first time).
285
-
286
-
#### Mutation
287
-
288
-
React will subscribe to sources after commit so that it can schedule updates in response to mutations. When a mutation occurs<sup>1</sup>, React will calculate an expiration time for processing the change, and will:
289
-
290
-
- Schedule an update for that expiration time.
291
-
- Update a root level entry for this source to specify the next scheduled expiration time.
292
-
- This enables us to avoid tearing within the root during subsequent renders.
293
-
294
-
¹ Component subscriptions may only subscribe to parts of the external source they care about. Updates will only be scheduled for component’s whose subscriptions fire.
328
+
<sup>1</sup> This case could occur during a mount or an update (if a new mutable source was read from for the first time).
295
329
296
-
#### Update (after subscription)
330
+
#### Reading from a source after subscription
297
331
298
332
React will eventually re-render when a source is mutated, but it may also re-render for other reasons. Even in the event of a mutation, React may need to render a higher priority update before processing the mutation. In that case, it’s important that components do not read from a changed source since it may cause tearing.
299
333
300
-
In order to process updates safely, React will track pending root level expiration times per source.
301
-
302
-
- ✓ If the current render’s expiration time is **≤** the stored expiration time for a source, it is **safe** to read.
303
-
- Store an updated snapshot value on `memoizedState`.
304
-
- If the current render expiration time is **>** than the root priority for a source, consider the config object.
305
-
- ✓ If the config object has not changed, we can re-use the **cached snapshot value**.<sup>1</sup>
306
-
- ✗ If the config object has changed, the **cached snapshot is stale**.
307
-
- Throw and restart the render.
308
-
309
-
¹ React will later re-render with new data, but it’s okay to use a cached value if the memoized config has not changed- because if the inputs haven’t changed, the output will not have changed.
310
-
311
-
#### React render new subtree
334
+
In the event the a component renders again without its subscription firing (or as part of a high priority update that does not include the subscription change) it will typically be able to re-use the cached snapshot.
312
335
313
-
React may render a new subtree that reads from a source that was also used to render an existing part of the tree. The rules for this scenario is the same as the initial mount case described above.
336
+
The one case where this will not be possible is when the `getSnapshot` function has changed. Snapshot selectors that are dependent on `props` (or other component `state`) may change even if the underlying source has not changed. In that case, the cached snapshot is not safe to reuse, and `useMutableSource` will have to throw and restart the render.
0 commit comments