Skip to content

Add prototype signals+path-tracking implementation - #2318

Open
markerikson wants to merge 73 commits into
masterfrom
feature/selector-perf-prototype-02-path-signals
Open

markerikson wants to merge 73 commits into
masterfrom
feature/selector-perf-prototype-02-path-signals

Conversation

@markerikson

@markerikson markerikson commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

This PR:

  • Adds a pair of signals-based alternative Provider/selector implementations, SignalProvider and useSignalSelector, designed to optimize the performance of subscriber notifications and selectors in larger apps (at the expense of larger bundle size)
    • adds a boatload of tests (of somewhat unknown quality)

Background

For the last several years I have been convinced that there is some way I can use signals and proxies to improve the subscriber+notification performance in large React-Redux apps.

Redux is a simple event emitter, with O(n) subscriber behavior. Every dispatched action triggers a loop over all subscriber callbacks, which in turn normally call getState() and a selector to determine if that useSelector or connect needs to re-render. React-Redux moves some of the tracking to be internal to itself and optimizes some subscription aspects, but it's fundamentally still calling O(n) callbacks every time.

As with React, this works fine for most apps, but breaks down at large scales. I've talked with teams that had 20,000 connected components, and at that scale just running the callbacks themselves is a major perf issue.

Proxies allow tracking access to nested fields. Signals allow automatically deriving dependencies. More magic, but signals libs automatically only update the dependencies that actually rely on a given updated value.

We can't and won't change the semantics of Redux in terms of immutable updates, subscribing to the store, etc. But, we can try to leverage some of these techniques inside of React-Redux, invisibly to the end user.

Previous Efforts

I've previously tried a couple different variations on this concept:

Neither got very far. They were mostly 1-day efforts that I never got back to. I think the autotracking PR sorta ran but already showed broken cases around field accesses.

Implementation Approach

This attempt pulls together a few different concepts from other libraries, as well as some seemingly new and unique approaches.

It uses a mixture of key path tracking ( "state.nested.field") and signals.

When a useSignalSelector hook mounts, it runs the actual selector (which as always could be a plain function, Reselect, or some other selector lib). That accesses fields in the state as usual.

However, the "state" the selector reads has been wrapped in a proxy. We track the accessed fields, and the terminal reads get turned into a signal for that path. The reads then set up standard signal dependencies, so that we effectively know which state paths this component depends on.

We have a root proxy and previous state value in SignalProvider. After an action is dispatched, we then do a diff between the old and new state, and calculate which state paths got changed. For those changed values, we call signal.set(newValue) internally. The updated signals then trigger the effect()s inside of useSignalSelector, which in turn trigger notifications of React.

There's a bunch of other internal implementation details I can write up later (like optimizing array updates via an entity-adapter-like ID tracking table, and reusing the same array method overrides approach I used on Immer), but that's the core idea.

Overall, the tradeoffs are:

  • The cost to mount a given component is higher because there's more subscriptions and values to set up
  • There is an up-front cost for every dispatch to do the root state reconciliation. That grows based on the size of the state + number of state paths being accessed.
  • We do assume that selectors are going to be consistent in which state paths they read.
  • If we can skip enough callbacks + selectors and that skipped cost is more than the time we spent doing the reconciliation, then this becomes a performance win
  • This definitely has a larger bundle size than plain useSelector. I haven't actually measured how big yet :) I'd assume on the order of 5-10K minified.

Usage

There's two new exports, SignalProvider and useSignalSelector. They ought to both be drop-in replacements for Provider and useSelector. Swap in SignalProvider, which passes down the existing context values + the signal data. Then call useSignalSelector in your components, same semantics, no user-visible difference.

Status and Notes

Status

Alpha-level, but it appears to be stable, is passing the new test suite so far, and is a significant improvement in my benchmarks suite (see below).

Haven't tried this in a real app yet, but the benchmarks setup is a suite of small example apps. So, between that and the unit tests, I'm pretty confident this runs in the cases I've tried so far.

Development Process

Getting this out up-front. The implementation here is 100% AI-written.

To be clear, this is engineered, not vibe-coded. I used my own standard "human-in-the-loop" AI engineering workflow. My ideas, fleshed out and implemented, over the course of a couple weeks. I reviewed all the lib code before each commit. (Maybe not all the tests :) )

I was also able to use AI to heavily revamp my longstanding React-Redux benchmarks repo: updating the build system, reviewing the existing scenarios, deleting dead ones, implementing better scenarios that provided better coverage of different use cases, and completely reworking what metrics I'm capturing so they're a lot more meaningful.

This is a POC, but it's code that I very intentionally drove the development of. I feel very confident that the current implementation actually runs as expected and implements the approaches I've worked through. I wouldn't even PR this unless I felt it was sufficiently solid enough to be public and tried out in real apps.

Performance

So, is this actually any better? :) (you would hope so... otherwise why would I even have taken the time to file this PR and write up this explanation?)

Based on my heavily revised and updated React-Redux benchmarks suite:

YES! This branch appears to be 20-40% faster in total scripting time per scenario than the current 9.x release

Here's a summary:

image

And the per-scenario details from my benchmarks suite:

Performance benchmark numbers
deeptree-nested-hooks
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   2149 │ 6931 │    652 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   2269 │ 6510 │    531 │ 10001 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   1626 │ 6077 │    549 │ 10005 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  96.1 │     0.3 │     1.3 │     563 │      1162 (0.59) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  53.6 │     0.2 │     1.1 │     583 │      1182 (0.85) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  62.0 │     0.2 │     1.0 │     581 │      1178 (0.30) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      1417 │     6 │          88 │      203 │  42 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      1261 │     2 │          91 │      158 │  34 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      1154 │     4 │         120 │      146 │  35 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │   244.1 │  439.0 │      1164 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │   202.3 │  806.0 │      1184 │    250.3 │ 477096 │    90.6 │ 476296 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │   202.0 │  153.4 │      1182 │      0.0 │      0 │     0.0 │      0 │     146.9 │ 1182 │   27.6 │ 8296 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
derived-selectors
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   1665 │ 1903 │     45 │ 10006 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1851 │ 2084 │     42 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   1048 │ 1254 │     37 │ 10014 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  11.6 │     0.4 │     0.6 │     341 │       399 (1.10) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  11.8 │     0.5 │     0.9 │     330 │       399 (1.36) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  62.1 │     0.1 │     0.2 │     338 │       398 (1.90) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      1169 │     4 │          38 │       44 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      1203 │     4 │          56 │       84 │   8 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       215 │     1 │         455 │       20 │   5 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    81.0 │  355.8 │       400 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    86.6 │  456.5 │       400 │    361.9 │ 111026 │    30.3 │ 110826 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    83.6 │  670.5 │       399 │      0.0 │      0 │     0.0 │      0 │     667.2 │  399 │  679.5 │ 704  │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
entity-list-array
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   1078 │ 1394 │     93 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1974 │ 2279 │     91 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    685 │ 1000 │     90 │ 10012 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │ 108.5 │     0.1 │     0.7 │     385 │       384 (1.29) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │ 106.9 │     0.2 │     0.8 │     359 │       358 (3.96) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  61.2 │     0.1 │     0.4 │     394 │       394 (0.41) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬──────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │  app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼──────┤
│ 8.1.1                   │       456 │     5 │           5 │       22 │  418 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼──────┤
│ 9.2.0                   │       410 │     3 │          28 │       28 │ 1247 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼──────┤
│ 9.2.0-signals-selectors │       401 │     4 │          73 │       26 │   27 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴──────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬───────┬─────────┬───────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │  Sel# │ EqCheck │   Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    77.3 │  418.5 │       385 │      0.0 │     0 │     0.0 │     0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    84.9 │ 1332.5 │       359 │   1331.6 │ 73263 │    20.7 │ 71173 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    82.9 │   79.5 │       396 │      0.0 │     0 │     0.0 │     0 │      77.1 │  396 │   70.5 │ 4566 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴───────┴─────────┴───────┴───────────┴──────┴────────┴──────┘
entity-list
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    619 │  927 │     88 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │    810 │ 1161 │    109 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    676 │  999 │     92 │ 10003 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  27.4 │     0.1 │     0.3 │     397 │       395 (0.36) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  33.9 │     0.1 │     0.6 │     396 │       395 (0.72) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  45.3 │     0.2 │     0.5 │     396 │       395 (0.37) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       416 │     4 │          10 │       31 │   6 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       457 │     2 │          23 │       66 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       422 │     3 │          58 │       28 │  15 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬───────┬─────────┬───────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │  Sel# │ EqCheck │   Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    80.2 │   59.8 │       397 │      0.0 │     0 │     0.0 │     0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    87.9 │  194.9 │       397 │     96.8 │ 87267 │    22.8 │ 85172 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    83.7 │   59.2 │       396 │      0.0 │     0 │     0.0 │     0 │      57.1 │  396 │   28.2 │ 4697 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴───────┴─────────┴───────┴───────────┴──────┴────────┴──────┘
forms
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    890 │ 5982 │    323 │ 10004 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1029 │ 6039 │    326 │ 10004 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    835 │ 4605 │    244 │ 10004 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │ 222.8 │     0.2 │     0.3 │     331 │       331 (0.43) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │ 229.5 │     0.2 │     0.3 │     329 │       329 (0.85) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │ 226.6 │     0.2 │     0.3 │     337 │       337 (0.16) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       630 │    12 │          40 │        7 │  47 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       643 │     3 │          20 │       18 │  47 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       671 │    10 │          20 │       16 │  37 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │    23.8 │  117.0 │       332 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │    22.7 │  257.0 │       329 │     77.3 │ 165659 │    36.6 │ 165158 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │    24.8 │   27.5 │       337 │      0.0 │      0 │     0.0 │      0 │      25.3 │  337 │    6.9 │ 838  │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
many-components-many-slices-bugged
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   7410 │ 7572 │     84 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   7800 │ 7946 │     77 │  9999 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   4739 │ 6456 │    890 │ 10005 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  42.9 │    14.3 │    25.8 │      50 │        48 (4.31) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  47.6 │    22.1 │    33.4 │      47 │        45 (8.82) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  56.1 │     0.6 │     1.0 │     748 │       747 (3.26) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      7379 │    42 │         154 │      168 │  36 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      7365 │    80 │         249 │      164 │  47 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2250 │    10 │         742 │     1543 │  10 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │   144.1 │   75.7 │        52 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │   131.4 │  311.3 │        49 │    196.2 │ 495000 │    98.8 │ 490000 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │  1677.1 │  764.0 │       750 │      0.0 │      0 │     0.0 │      0 │     759.5 │  750 │   27.7 │ 5750 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴──────┘
many-components-many-slices
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   4990 │ 6799 │    937 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   7438 │ 9150 │    890 │ 10002 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   4898 │ 6737 │    956 │ 10004 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  42.6 │     0.6 │     1.0 │     750 │       748 (3.21) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  49.0 │     0.6 │     0.9 │     731 │       729 (7.29) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  60.9 │     0.7 │     1.1 │     747 │       745 (3.21) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      2557 │    13 │         160 │     1608 │  12 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      2310 │     7 │         198 │     1480 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2439 │     8 │         835 │     1421 │  11 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬─────────┬─────────┬─────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │    Sel# │ EqCheck │     Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │  1671.2 │  742.0 │       754 │      0.0 │       0 │     0.0 │       0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │  1444.4 │ 3907.5 │       734 │   1218.1 │ 3675734 │   676.9 │ 3670734 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │  1594.0 │  810.3 │       750 │      0.0 │       0 │     0.0 │       0 │     804.9 │  750 │   30.9 │ 5750 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴─────────┴─────────┴─────────┴───────────┴──────┴────────┴──────┘
many-components-same-slice
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   6715 │ 7666 │    676 │ 10004 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   7138 │ 8008 │    605 │ 10000 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   6846 │ 7790 │    661 │ 10003 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  32.4 │    12.7 │    20.3 │      45 │        43 (1.23) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  37.2 │    17.9 │    24.6 │      45 │        43 (5.83) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  40.3 │    10.3 │    17.5 │      46 │        44 (5.00) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      6979 │    15 │         158 │       20 │   9 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      6993 │    16 │         237 │       23 │  15 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      6956 │    21 │         181 │       13 │  18 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬────────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig#   │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼────────┤
│ 8.1.1                   │     4.5 │   53.9 │        47 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0      │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼────────┤
│ 9.2.0                   │     4.7 │  271.7 │        47 │    100.4 │ 475000 │    88.4 │ 470000 │       0.0 │    0 │    0.0 │ 0      │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼────────┤
│ 9.2.0-signals-selectors │     5.5 │  232.6 │        48 │      0.0 │      0 │     0.0 │      0 │     231.7 │   48 │   88.1 │ 245000 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴────────┘
multi-selector-component
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   3309 │ 4575 │    609 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   4441 │ 5590 │    551 │ 10014 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   3054 │ 4278 │    598 │ 10012 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  25.1 │     4.4 │    10.9 │     131 │       315 (1.41) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  30.2 │     6.9 │    18.0 │     124 │       289 (5.08) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  41.7 │     4.4 │    10.9 │     133 │       317 (0.49) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      2717 │    19 │         222 │       38 │  20 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      2514 │    12 │         323 │       38 │  39 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2639 │    12 │         171 │        9 │  48 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬─────────┬─────────┬─────────┬───────────┬──────┬────────┬───────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │    Sel# │ EqCheck │     Eq# │ Reconcile │ Rec# │ SigSel │ Sig#  │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼───────┤
│ 8.1.1                   │    33.3 │  408.2 │       316 │      0.0 │       0 │     0.0 │       0 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0                   │    34.3 │ 1432.1 │       289 │    378.0 │ 1404248 │   359.4 │ 1400248 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    36.5 │  116.3 │       317 │      0.0 │       0 │     0.0 │       0 │     114.5 │  317 │   57.1 │ 71065 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴─────────┴─────────┴─────────┴───────────┴──────┴────────┴───────┘
one-component-many-slices
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   6337 │ 6603 │     50 │ 10003 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   6314 │ 6574 │     50 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   9638 │ 9845 │     49 │ 10006 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │   2.1 │     0.0 │     0.1 │     770 │       769 (7.78) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │   2.1 │     0.0 │     0.1 │     770 │       769 (7.74) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │   2.5 │     0.0 │     0.1 │     752 │      750 (12.43) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       300 │     1 │          17 │     5913 │  24 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       283 │     2 │          21 │     5845 │  13 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       326 │     7 │        3211 │     6063 │  13 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬──────┬─────────┬──────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │ Sel# │ EqCheck │  Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼──────┼─────────┼──────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │  5995.3 │   22.7 │       773 │      0.0 │    0 │     0.0 │    0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼──────┼─────────┼──────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │  5954.2 │   27.5 │       772 │      1.6 │ 1545 │     0.6 │ 1544 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼──────┼─────────┼──────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │  6123.6 │ 3267.0 │       755 │      0.0 │    0 │     0.0 │    0 │    3263.0 │  755 │   14.8 │ 756  │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴──────┴─────────┴──────┴───────────┴──────┴────────┴──────┘
rapid-dispatch
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    806 │ 1077 │     93 │ 10007 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1187 │ 1456 │     90 │ 10011 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    867 │ 1126 │     91 │ 10005 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  11.4 │     0.5 │     1.1 │     126 │       619 (0.13) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  13.0 │     0.7 │     2.0 │     131 │       617 (0.64) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  16.8 │     0.5 │     1.3 │     126 │       595 (0.15) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       697 │     3 │          13 │       19 │   6 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       756 │     1 │          34 │       33 │  10 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       728 │     1 │          32 │       15 │   7 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬───────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig#  │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 8.1.1                   │    37.9 │   43.7 │       628 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0                   │    47.8 │  350.9 │       626 │     91.8 │ 332109 │    74.4 │ 331609 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    37.9 │   49.9 │       604 │      0.0 │      0 │     0.0 │      0 │      48.4 │  604 │   22.9 │ 19395 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴───────┘
rtkq-separate-queries
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │    880 │ 2454 │     63 │ 10009 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   1022 │ 2715 │     64 │ 10007 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    785 │ 2173 │     55 │ 10010 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  22.4 │     0.7 │     2.8 │     205 │      2512 (0.44) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  22.4 │     0.8 │     3.1 │     212 │      2705 (0.46) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  24.7 │     1.3 │     3.8 │     172 │      2209 (0.45) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       426 │     5 │          16 │     1171 │  16 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       428 │     5 │          30 │     1343 │  15 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       374 │     1 │         108 │      981 │  15 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬───────┬─────────┬───────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │  Sel# │ EqCheck │   Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │  1099.0 │    3.9 │      2514 │      0.0 │     0 │     0.0 │     0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │  1217.3 │    5.2 │      2707 │    255.1 │ 49569 │    14.7 │ 49369 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼───────┼─────────┼───────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │   977.9 │    1.9 │      2210 │      0.0 │     0 │     0.0 │     0 │     116.2 │  234 │   84.8 │ 3385 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴───────┴─────────┴───────┴───────────┴──────┴────────┴──────┘
selective-update
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   3152 │ 4278 │    552 │ 10012 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   3968 │ 5052 │    521 │ 10006 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   3095 │ 4225 │    555 │ 10000 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │  22.3 │     0.7 │     1.1 │     397 │       396 (0.62) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │  25.3 │     0.9 │     1.3 │     397 │       396 (2.43) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │  28.2 │     0.6 │     1.0 │     396 │       395 (0.48) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │      2774 │     6 │         112 │       24 │  18 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │      2824 │     8 │         150 │       24 │  30 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │      2817 │     6 │          72 │        6 │  12 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬────────┬─────────┬────────┬───────────┬──────┬────────┬───────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │   Sel# │ EqCheck │    Eq# │ Reconcile │ Rec# │ SigSel │ Sig#  │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 8.1.1                   │    28.1 │  216.6 │       398 │      0.0 │      0 │     0.0 │      0 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0                   │    33.5 │  932.5 │       398 │    263.6 │ 877600 │   202.3 │ 875600 │       0.0 │    0 │    0.0 │ 0     │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼────────┼─────────┼────────┼───────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │    28.2 │  157.4 │       397 │      0.0 │      0 │     0.0 │      0 │     155.1 │  397 │   73.7 │ 81400 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴────────┴─────────┴────────┴───────────┴──────┴────────┴───────┘
tree-view
  All times in ms
┌─────────────────────────┬────────┬──────┬────────┬───────┐
│ Version                 │ Script │ Task │ Layout │  Wall │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 8.1.1                   │   1789 │ 8784 │    303 │ 10006 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0                   │   3055 │ 9633 │    298 │ 10008 │
├─────────────────────────┼────────┼──────┼────────┼───────┤
│ 9.2.0-signals-selectors │   1415 │ 7824 │    299 │ 10010 │
└─────────────────────────┴────────┴──────┴────────┴───────┘
┌─────────────────────────┬───────┬─────────┬─────────┬─────────┬──────────────────┐
│ Version                 │ Mount │ Avg Upd │ p95 Upd │ Renders │ Dispatches (avg) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 8.1.1                   │ 139.6 │     0.3 │     0.6 │     321 │       319 (3.22) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0                   │ 147.4 │     0.3 │     0.9 │     293 │       292 (7.99) │
├─────────────────────────┼───────┼─────────┼─────────┼─────────┼──────────────────┤
│ 9.2.0-signals-selectors │ 233.7 │     0.3 │     1.0 │     341 │       339 (1.62) │
└─────────────────────────┴───────┴─────────┴─────────┴─────────┴──────────────────┘
┌─────────────────────────┬───────────┬───────┬─────────────┬──────────┬─────┐
│ Version                 │ react-dom │ react │ react-redux │ redux/tk │ app │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 8.1.1                   │       778 │     9 │         218 │      360 │  42 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0                   │       788 │     9 │         196 │      318 │  52 │
├─────────────────────────┼───────────┼───────┼─────────────┼──────────┼─────┤
│ 9.2.0-signals-selectors │       617 │     9 │         187 │      358 │  61 │
└─────────────────────────┴───────────┴───────┴─────────────┴──────────┴─────┘
┌─────────────────────────┬─────────┬────────┬───────────┬──────────┬─────────┬─────────┬─────────┬───────────┬──────┬────────┬──────┐
│ Version                 │ Reducer │ Notify │ Callbacks │ Selector │    Sel# │ EqCheck │     Eq# │ Reconcile │ Rec# │ SigSel │ Sig# │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 8.1.1                   │   412.0 │  620.2 │       321 │      0.0 │       0 │     0.0 │       0 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0                   │   356.1 │ 2002.7 │       295 │    543.5 │ 1494046 │   283.0 │ 1488952 │       0.0 │    0 │    0.0 │ 0    │
├─────────────────────────┼─────────┼────────┼───────────┼──────────┼─────────┼─────────┼─────────┼───────────┼──────┼────────┼──────┤
│ 9.2.0-signals-selectors │   433.1 │  118.5 │       341 │      0.0 │       0 │     0.0 │       0 │     116.5 │  341 │   30.5 │ 5449 │
└─────────────────────────┴─────────┴────────┴───────────┴──────────┴─────────┴─────────┴─────────┴───────────┴──────┴────────┴──────┘

So, overall, it's meaningfully faster in 12 of the 14 benchmark scenarios, one is even, and the only case where it loses is an unrealistic "one component subscribed to hundreds of slices" scenario.

Next Steps?

At this point I think this is ready to be tried out in real-world apps. I'm sure there's edge cases that we aren't handling, just because real code is complex and we're doing tricky work here :) I'd love to know where this breaks, and if it appears to actually improve perf.

I need to do some testing to see how much this increases bundle size, as well as reviewing the overall test cases here and seeing what else we ought to cover.

For the record I do not plan on actually replacing the existing Provider and useSelector implementations. This is an alternative, not a replacement, primarily aimed at apps with large codebases and many subscriptions. I might ship an alternate entry point that would swap to the signal versions, and if you want to use them everywhere you could alias something like "react-redux/signals" as the import in your bundler setup.

CI Builds

As usual, see the CodeSandbox CI link below for the latest commit preview build.

Current one is:

# yarn 1
yarn add https://pkg.csb.dev/reduxjs/react-redux/commit/7288029e/react-redux
# yarn 2, 3
yarn add react-redux@https://pkg.csb.dev/reduxjs/react-redux/commit/7288029e/react-redux/_pkg.tgz
# npm
npm i https://pkg.csb.dev/reduxjs/react-redux/commit/7288029e/react-redux

@codesandbox-ci

codesandbox-ci Bot commented Jun 9, 2026

Copy link
Copy Markdown

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Size Change: +186 kB (+501.08%) 🆘

Total Size: 223 kB

📦 View Changed
Filename Size Change
dist/cjs/react-redux-signals.development.cjs 29.3 kB +29.3 kB (new file) 🆕
dist/cjs/react-redux-signals.production.min.cjs 10.6 kB +10.6 kB (new file) 🆕
dist/cjs/react-redux.development.cjs 30.3 kB +20.6 kB (+213.23%) 🆘
dist/cjs/react-redux.production.min.cjs 10.8 kB +7.05 kB (+186.88%) 🆘
dist/cjs/signals.js 147 B +147 B (new file) 🆕
dist/react-redux-signals.browser.mjs 10.4 kB +10.4 kB (new file) 🆕
dist/react-redux-signals.legacy-esm.js 30 kB +30 kB (new file) 🆕
dist/react-redux-signals.mjs 29 kB +29 kB (new file) 🆕
dist/react-redux.browser.mjs 10.6 kB +7.06 kB (+197.87%) 🆘
dist/react-redux.legacy-esm.js 31 kB +20.8 kB (+206.03%) 🆘
dist/react-redux.mjs 29.9 kB +20.6 kB (+223.48%) 🆘
dist/rsc.mjs 584 B +17 B (+3%)
ℹ️ View Unchanged
Filename Size
dist/cjs/index.js 140 B

compressed-size-action

@markerikson
markerikson force-pushed the feature/selector-perf-prototype-02-path-signals branch from 49f63bc to dda99e1 Compare June 9, 2026 04:29
@GabbeV

GabbeV commented Jun 9, 2026

Copy link
Copy Markdown

Saw you working on this from your post on bluesky and got interested in how it works so had a look.

I think I found a limitation to this approach that I think is fundamentally unavoidable. Maybe you've figured it out already and intend to document it as a gotcha.

If the logic in the selector relies on object identity then the proxies can't really catch that dependency as proxies can't see Object.is or === etc. You can't assume a object is used just because it is read either as that would make everything depend on the root object that changes all the time defeating the entire optimization.

https://codesandbox.io/p/sandbox/festive-meadow-q7rk68?file=%2Fsrc%2FApp.tsx%3A7%2C15

@dai-shi

dai-shi commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Nice. Avoiding O(n) subscription is great, because proxy-memoize approach is still O(n).
If we were okay with leaking proxies to render functions, useTrackedState or useSignalState might be possible too.
(BTW, I'm planning O(1) subscription (edit: well, it might be misleading. ref: pmndrs/valtio#1161 ) for Valtio v3.)

@markerikson

markerikson commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@GabbeV Having trouble opening that sandbox and not sure if it's network issues, CSB issues, or just isn't there. Could you post the relevant snippets? I'll try to repro separately in the meantime.

@dai-shi eeenteresting! would love to see what you have in mind for O(1) behavior!

@GabbeV

GabbeV commented Jun 10, 2026

Copy link
Copy Markdown

@markerikson
Here is the main part of the repro

import { configureStore } from "@reduxjs/toolkit";
import {
  SignalProvider,
  useSignalSelector,
  useSelector,
  useDispatch,
} from "react-redux";

const a = { id: "a" };
const b = { id: "b" };

const initialState: State = {
  items: [a, b],
  current: a,
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case "selectA":
      return { ...state, current: state.items[0] };

    case "selectB":
      return { ...state, current: state.items[1] };

    default:
      return state;
  }
}

const store = configureStore({ reducer });

function selectCurrentByReference(state) {
  return state.items.find((item) => item === state.current);
}

export default function App() {
  return (
    <SignalProvider store={store}>
      <AppInner />
    </SignalProvider>
  );
}

function AppInner() {
  const dispatch = useDispatch();

  return (
    <main>
      <button onClick={() => dispatch({ type: "selectA" })}>Select A</button>
      <button onClick={() => dispatch({ type: "selectB" })}>Select B</button>

      <SignalResult />
      <NormalResult />
    </main>
  );
}

function SignalResult() {
  const current = useSignalSelector(selectCurrentByReference);
  return <p>signal active: {current?.id ?? "none"}</p>;
}

function NormalResult() {
  const current = useSelector(selectCurrentByReference);
  return <p>normal active: {current?.id ?? "none"}</p>;
}

@markerikson
markerikson force-pushed the feature/selector-perf-prototype-02-path-signals branch from 65ed737 to 5f96e70 Compare June 11, 2026 10:17
@markerikson

markerikson commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@GabbeV okay, did some research and work around object identity and edge cases.

I've added an unwrap() util that can be used to help with reference-based comparisons. However, the {current: someSharedReference} + array.find(item => item === current) comparison case is tricky enough that it seems like we can't cover it automatically. The options are either:

  • Auto-wrap all array values in proxies, which kills perf
  • Do a double-call on find() with and without wrapped values just in case the second call works
  • use unwrap() if it actually helps here

Generally, find() with a non-nested reference comparison is somewhat non-idiomatic in Redux. I think typically you'd be doing a item.id === selectedId check anyway. (Plus... if you're looking for an item by its reference, doesn't that mean you have it already? :) )

@GabbeV

GabbeV commented Jun 11, 2026

Copy link
Copy Markdown

@markerikson sounds like there are more trickiness there than I even realized. It for sure is unidiomatic redux to have logic around object identity at all so I don't think this is a deal breaker. It is just an example showing that non deterministic logic isn't the only thing preventing this from being a perfect drop in replacement without at least some disclaimers.

I'm not sure how you would get away with not wrapping each accessed element in an array find without missing a bunch of real dependencies in a case like useSignalSelector(s => a.find(x => x.done)?.name). Every item you scanned over could become done later so you do need dependency on each as well as dependency on new items getting added to the array. However using an array in my example was not part of the issue I was trying to show at all.

The issue I was getting at isn't even that === is always false, it is that === won't register a dependency. I think unwrap is kind of a nice solution to both of those issues though. However I think it needs to not only give access to the wrapped value but also register a dependency on the object changing. Imagine a selector like this useSignalSelector(s => s.a === s.b ? "true" : "false"). If a and b are objects the selector wouldn't get registered to rerun as they aren't "terminal values".

@markerikson

Copy link
Copy Markdown
Contributor Author

@GabbeV yeah, thanks for bringing up these edge cases!

I think I already had the useSignalSelector(s => s.a === s.b ? "true" : "false") case handled in this PR. We actually track both leaf values and their immediate parent, and that seems to cover that case.

As for the array methods: yeah, I had added the array method wrappers that delegate to the underlying array, specifically to avoid the overhead of creating proxies every time you iterate through an array even if you're just reading. Did some more thinking on this, and I just updated the PR to auto-track the array itself any time you call one of the methods. The key brainstorm I had here is that this whole PR isn't the entire state change detection method - it's a set of heuristics to figure out if it's safe to skip the callbacks and selectors we are pretty sure would be irrelevant because their dependencies haven't changed. So, we need to make sure we never miss an update, but it's okay if we still run some selectors that report "no change, don't re-render".

That means that always marking an array as being accessed and needing consideration ought to handle cases like array.find/filter(), without needing to create proxies for all the individual fields. The latest commit has tests for those cases.

@markerikson
markerikson force-pushed the feature/selector-perf-prototype-02-path-signals branch from 18e22cc to 97379d5 Compare June 11, 2026 16:06
@GabbeV

GabbeV commented Jun 11, 2026

Copy link
Copy Markdown

@markerikson allowing false positive executions sounds like a smart approach for the arrays if wrapping each item is prohibitive performance wise. I'm wondering if maybe you could reuse proxies as an alternative solution if the performance issue is because of allocations? Not for different objects but for the same object between different selectors. Basically you save the proxy per object in a weakmap or on a hidden symbol property on the object it self.

Maybe I am missing something but I believe the issue I'm describing with useSignalSelector(s => s.a === s.b ? "true" : "false") is fundamentally unsupportable with proxies unless rewritten to something like useSignalSelector(s => unwrapAndTrack(s.a) === unwrapAndTrack(s.b) ? "true" : "false").

Here is a more complex example:

useSignalSelector(s => {
  const a = s.a
  const aFooBar = a.foo.bar
  const b = s.b
  const bFooBar = b.foo.bar
  
  if (a === b) return "same"
  if (aFooBar === bFooBar) return "similar"
  return "different"
})

The proxies can see that you are traversing down s.a.foo.bar and s.b.foo.bar but they can't possibly see that you are checking the identity between s.a and s.b as there is no proxy trap for equality. Imagine the object starting of as "same" but then some property unrelated to foo.bar changes on one of them so that they are no longer the "same" but still "similar".

if (unwrap(a) === unwrap(b)) return "same" would just solve that branch failing because of the unstable identity of proxy wrappers but does not cause the selector to rerun for object changes unless unwrap also causes it to subscribe to any change on the unwrapped value similar to your array solution.

@markerikson

Copy link
Copy Markdown
Contributor Author

@GabbeV I'm inclined to say this is a limitation. Obviously we want selector code to work out of the box. But especially with proxies, we can't cover literally every bit of JS syntax. As it is, we document the "zombie child" case for useSelector because it's a known limitation in how subscriptions work. My biggest goals here are cutting down on subscription overhead for large apps. It's opt-in at the user level by using useSignalSelector, so it's okay if there's some kind of edge case limitation.

@nstadigs

Copy link
Copy Markdown

Would this make supporting "async react"/concurrency easier or harder?

@markerikson

Copy link
Copy Markdown
Contributor Author

@nstadigs I'm not sure how it fits architecturally at this point.

One reason I'd held off trying to push this idea forward was that the useStore / "concurrent store" prototyping work was going on a few months ago, but as you've seen that appears to be stalled / dead atm. Given that, I figured it was worth at least seeing if I could get this concept to work and if there were perf benefits.

I'm not planning to ship this soon (would need a lot more benchmarking and real-world testing first), but big-picture I wouldn't want to hold off on this "just in case" the React team somehow decides to actually solve that use case. The combination of Meta layoffs, team shuffling, lack of response to the useStore PR, and apparent internal discussion of "is this even the right API design?" says to me that ain't happening for the foreseeable future, so I'm not going to sit around waiting for something to happen there first.

@adardesign

Copy link
Copy Markdown

♥️

@markerikson

Copy link
Copy Markdown
Contributor Author

FWIW the last commits I pushed that relate to tracking arrays appear to have lost most of the perf gains :( Debating options around correctness vs perf, but also running around and juggling time to work on this.

As things stand atm: I'd absolutely appreciate feedback on the correctness of the current implementation in real apps, as well as any more edge cases like the ones discussed above where it turns out this breaks or doesn't propagate updates as expected. As of the current commit I expect it's probably not much faster than useSelector, and I'm still looking into that, but getting feedback on how it runs overall would be helpful.

@pkg-pr-new

pkg-pr-new Bot commented Jul 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/react-redux@c6ffabf -D
yarn add https://pkg.pr.new/react-redux@c6ffabf.tgz -D
pnpm add https://pkg.pr.new/react-redux@c6ffabf.tgz -D
bun add https://pkg.pr.new/react-redux@c6ffabf.tgz -D

commit: c6ffabf

@markerikson

Copy link
Copy Markdown
Contributor Author

Just pushed a sizeable update that adds a lot more diff tracking logic. This appears to restore the overall perf improvements I was seeing earlier.

I'll try to post some actual benchmarks later, as well as some analysis on what this does to bundle size.

The logic here is complex, but feels plausible given the problem space.

Next step is to try to get some more real-world-ish usage scenarios put together.

@mikeduminy

mikeduminy commented Jul 29, 2026

Copy link
Copy Markdown

If the API design is still open to discussion I'd like to propose removing the need to wrap the render tree in a <SignalProvider />. Does wrapping it give any benefit? From my perspective at companies running multiple redux stores this Provider mechanic is cumbersome. I realise multi-stores is not the norm but I think if selectors could work independent of the Provider the API would incur lower cognitive load. For instance:

// create selectors bound to a specific store
const store = configureStore()
const { useSignalSelector, useSelector } = createBoundHooks(store)

// use the selectors even if the React tree is not wrapped in a Provider
const value = useSelector((state) => state.value)

In my mind the Provider is only useful to limit propagation of state updates to sub-trees of the whole React tree, otherwise it is just cumbersome. I'd prefer to opt-in to using a Provider if I needed better performance of scoped updates. Additionally, signal selectors are probably not affected by the use of a higher-level Provider, or need not be.

I haven't confirmed any of these and I'd love to get feedback on the general idea.

EDIT: I made a hacky proof of concept work and got all tests to pass here - mikeduminy@3141ffa

@markerikson

Copy link
Copy Markdown
Contributor Author

@mikeduminy I can take a look at that, but one of my explicit goals for this effort is to make useSignalSelector as much of a drop-in replacement as possible. That includes being able to mix and match standard useSelector with useSignalSelector incrementally in an app. So, SignalProvider is intentionally written as "Provider with the extra logic added", and I would be very hesitant to do anything different.

@EskiMojo14

EskiMojo14 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

we also want you to use context for your store because it makes testing significantly simpler
instead of needing to manually reset your store each time, you can just provide a fresh store for each test (as shown in our testing guide)

@mikeduminy

mikeduminy commented Jul 29, 2026

Copy link
Copy Markdown

@markerikson in the approach I've followed there the SignalProvider can still be used if needed. But if you create a "bound" selector it'll just ignore it. If you don't create a "bound" selector it will read from the Provider as usual. I'm sure there are ways we can make that even more ergonomic.

If we had Provider mechanics and people could also use selectors without Providers then isn't it a win-win?
In other words the selector hooks try find the nearest parent Provider context, and if they don't find it they use the bound context if provided. It's like an opt-in feature.

@EskiMojo14 I'm not sure I follow, why wouldn't this still be easy to test? You can still wrap your tests with a store creator and use the bound selector from that.

@EskiMojo14

Copy link
Copy Markdown
Collaborator

how would the component get the bound hook?

@mikeduminy

Copy link
Copy Markdown

Ah I see. Sure you would need to adjust your testing strategy slightly from the current recommendation if you wanted to test the bound hook specifically (beforeEach store.reset, jest.doMock injection of the new hook, setting the hook on the context, etc).

In my proposal however I'm not saying that the Provider mechanics need to disappear, just that they are not really needed for this new hook (and arguably for normal hooks too). Equally, the API does not needs to look exactly as described -- it represents a preferred possibility but I'd be just as happy to have a different one. For instance:

// create selectors bound to a specific store
const store = configureStore()
const useBoundSelector = createSelectorHook(store.toContext())

// use bound hook with or without a provider
const value = useBoundSelector((state) => state.value)

I totally buy the intent to keep this simple and as a drop-in replacement. I guess I'm advocating for exposing a createSignalSelectorHook just like there is a createSelectorHook today that can do almost exactly what I want.

@EskiMojo14

Copy link
Copy Markdown
Collaborator

I'm sure there'll be a createSignalSelectorHook to enable custom contexts, but treating the store as a singleton is enough of a footgun that i don't think we should provide official support for it.

… leaks

Uncommitted components never get a useSyncExternalStore subscribe call,
so nothing disposes the render-phase eager deep build for ungateable
hooks. Tests assert the desired no-leak behavior and currently fail:
- suspend-forever (with and without unmount)
- suspend-then-commit (first render attempt's bridge is orphaned)
- abandoned/suspended transitions
- StrictMode mount (useMemo factory double-invoke orphans one bridge)

Gateable coarse-tier hooks, prune-based recovery, and StrictMode
suspend documentation cases included. 7 fail / 3 pass as of this commit.
alien-signals is the engine; the facade's only real consumer of
injectability is the reconcile benchmark stub. Keep SignalEngine as an
internal DI seam on createPathSignalRegistry/reconcileState, drop the
SignalProvider engine prop, the context field and its validation check,
and the alienEngine/SignalEngine/SignalScope public exports. The hook
now imports signal/computed/effect from reactiveSystem directly.
- Drop the unread 'version' counter and the empty cleanup useEffect in
  useSignalSelector
- Move the selector-computed comment block onto selectorComputed; it had
  been welded to the leafTracker declaration
- Move unwrap()'s jsdoc off getProxyTarget and onto unwrap
- Fix createTrackingProxy's jsdoc: the proxy cache persists across
  evaluations (that persistence is the point), it is not per-evaluation
- Extract bumpVersion() in pathSignalRegistry (was triplicated)
- Add joinPath()/keysMetaPath() in arrayKeys and use them in
  trackingProxy and diff, replacing seven inline constructions of the
  same path shapes
The registry and the diff baseline are now keyed to the store via
useMemo, matching how stock Provider rebuilds its Subscription. Before
this, a store swap kept the old registry and diffed the old store's
state against the new store's on the first dispatch.

Also flips the hook function's generic order to <A, S> so it matches
both SignalProviderProps and stock Provider (it was <S, A> for no
reason), and lazily initializes state instead of allocating a registry
on every render.

New storeSwap.spec.tsx pins the behavior (verified the diff-baseline
test fails without the fix) and a type test pins the generic order.
- diff.spec.ts: capture signals before diffing instead of getOrCreate-seeding
  the expected value, which passed vacuously; assert outcomes in the two
  previously assertion-free edge-case tests
- Exact counts replace loose bounds in engine, useSignalSelector,
  devModeChecks, and trackingProxy specs
- Replace getByTestId().toBeTruthy() with content assertions in
  suspenseUncommitted/zombieChildren/ssr specs
- pathSignalRegistry.spec.ts: new unit coverage for ensurePrefix/hasPrefix,
  pruneChildren, release + flushReleases (incl. resurrection and stale-node
  guards), column/structure signal tracking and release cleanup, debugStats
- edgeCasesReact.spec.tsx: selector throw-then-recover (pendingError retry
  leg) and dispatch-during-selector tests
- Add reselect as a devDependency
- New test/signals/reselect.spec.tsx running in both modes: weakMapMemoize
  cache hits must not wipe dependency tracking, exact render/recompute
  counts for memoized and layered selectors, lruMemoize parity, shared
  derived object references across components
- New src/index-signals.ts: presents SignalProvider/useSignalSelector
  under the stock names Provider/useSelector/createSelectorHook so a
  bundler alias swaps an app onto the signals implementation
- Build outputs for all formats (modern ESM, legacy ESM, browser,
  CJS dev/prod) plus dist/cjs/signals.js env switcher
- package.json exports map entry for './signals'
…lices

- in-place mutation: silent staleness, recovery via honest change or coarse promotion
- non-plain values (Map/class): ref-level gating semantics
- selectors that mutate: proxy TypeErrors, and the mount-time probe gap where
  a sorting selector silently mutates real store state before promotion
- rAF-style batched notification pattern
- probe proxy wraps nested objects in dev-only write guards, so a
  mutating selector throws at mount instead of silently corrupting
  store state before promotion
- all write traps throw a named error ('selectors must not mutate
  state') instead of the engine's 'trap returned falsish' TypeError
- probe proxy now uses an unfrozen shell target so the guard-returning
  get trap satisfies ES Proxy invariants on frozen state
- fix reducer state type in reselect.spec.tsx typecheck
- Regenerate pnpm-lock.yaml (adds alien-signals and reselect)
- Drop the alien-signals/system tsconfig paths hack; bundler resolution reads the exports map
- Point test:both at pnpm
- Allow bare property reads in the tracking-proxy specs, where they register dependencies
- Reformat test files with oxfmt
Keeps headings whose slugs match the old single-page anchors (#useselector, #usedispatch, #usestore, #custom-context, #usage-warnings) so existing external deep links still land on a relevant blurb.
Adds lowercase redirect aliases for the camelCase page URLs and marks batch as deprecated pending removal in the next major.
…ined

With both entries in one Rolldown build, the shared code was hoisted into a
src.* chunk and react-redux.mjs became a re-export shim without a sourcemap,
which broke the publish-ci bundled-modules snapshot.
Rolldown and Rollup cannot drop a pure call whose result is destructured
at module scope. Keeping createReactiveSystem's result on one variable and
reading it at the call sites lets an app that imports only the stock hooks
shed alien-signals and the effect layer entirely (probe: 20.1 KB -> 10.7 KB).
- examples/publish-ci/rr-tree-shaking: Vite app using only Provider,
  hooks, and connect from the main entry
- check-tree-shaking.mjs fails the build if any signals identifiers
  survive in the output
- added to the test-published-artifact-local matrix
@markerikson
markerikson force-pushed the feature/selector-perf-prototype-02-path-signals branch from c7a1d76 to 63fb0eb Compare September 19, 2026 03:22
@markerikson
markerikson marked this pull request as ready for review September 19, 2026 04:21
@markerikson markerikson changed the title [DRAFT] Add prototype signals+path-tracking implementation Add prototype signals+path-tracking implementation Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants