v9.4.0-alpha.2
This alpha release fixes two bugs in useSignalSelector: stale values rendered in the gap between a dispatch and the store notification, and tracking proxies leaking out of values a selector held onto from an earlier run. It also cuts the cost of spreading or enumerating objects inside selectors, and adds test coverage for using useSignalSelector with RTK Query's hooks. The stock Provider and useSelector are unchanged.
npm install react-redux@alpha
This is still an alpha. Please try it out and give us feedback! The alpha.0 notes cover what useSignalSelector is and how to opt in.
Once a useSignalSelector hook had its full dependency graph built, its getSnapshot returned a cached result that only updated when the store notified subscribers. Normally that happens synchronously inside dispatch(), but there are several cases where React renders the component before that notification arrives:
- RTK's
configureStoreincludes theautoBatchEnhancer, which delays notifications for batched actions (like RTK Query'spendingandfulfilledactions) until the next animation frame - a parent re-renders for its own reasons in that gap
- a
useSelectorcomponent and auseSignalSelectorcomponent in the same tree render in the same pass - an action dispatched from a layout effect, which React picks up in its post-commit snapshot check
In all of these, useSignalSelector could render the previous value, and a useSelector and a useSignalSelector reading the same field could disagree within one render.
getSnapshot now checks whether store.getState() has moved since the cached result was computed. If it has, it re-runs the selector against the current state. useSignalSelector also now passes React a new getSnapshot when the selector reference changes, matching useSelector, so React's post-commit consistency check runs in the same cases it does for useSelector.
The render-time fallback path above runs the selector against raw state, and we assumed its result could never contain tracking proxies. That assumption was wrong. A selector that holds onto a value from an earlier tracked run, via a closure or a ref, can return a proxy from that earlier run.
RTK Query does exactly this: its query hooks keep the last result so they can show the previous data while a new arg loads. With a stable selectFromResult, the component received a proxy of the previous data during that window instead of the raw state object. Results from that path are now unwrapped the same way as the main path.
Spreading an object in a selector ({ ...state.user }), or using Object.keys/values/entries, Object.assign, JSON.stringify, or for...in, previously recorded a dependency on the object's key list plus one dependency per field. Reading every field of an immutably-updated object is equivalent to depending on the object's identity, so a full enumeration of a nested object now collapses into a single identity dependency on that object.
Partial enumerations still track precisely: Object.keys(obj).length, or reading only some fields after enumerating, keeps field-level dependencies. The root state object and arrays are excluded, since their tracking already works differently.
This mostly shows up with RTK Query, whose selectors spread each cache entry. In our RTK Query benchmark, useSignalSelector time per dispatch dropped by about 20-30%.
We've added an interop test suite that runs RTK Query's hooks on top of useSignalSelector, via a custom createApi:
import {
buildCreateApi,
coreModule,
reactHooksModule,
} from '@reduxjs/toolkit/query/react'
import { useDispatch, useSignalSelector, useStore } from 'react-redux'
export const createApi = buildCreateApi(
coreModule(),
reactHooksModule({
hooks: { useDispatch, useSelector: useSignalSelector, useStore },
}),
)
The tests cover query phases, selectFromResult, mutations and invalidation, skip, arg changes, and render counts, which match useSelector exactly. The types line up too: useSignalSelector satisfies the hooks.useSelector option type with no casts.
Writing those tests turned up two bugs on the RTK side, both fixed in RTK 2.13.0. useQueryState passed a new inline selector to useSelector on every render, which forced useSignalSelector to re-run the full tracked selector on every render. It also let isSuccess flip on unrelated re-renders during a refetch after an error. With 2.13.0, useSignalSelector selector runs per dispatch in our RTK Query benchmark showed a ~30% drop vs alpha.2. We recommend 2.13.0+ if you try this setup.
10 s per scenario, this release vs 9.3.0, Chrome, react-dom/profiling, RTK 2.12.
| Scenario | Script | Blocked | Notes |
|---|---|---|---|
| tree-view | -54% | -77% | |
| many-components-many-slices | -31% | -54% | |
| realistic-slice-count | -27% | -80% | |
| deeptree-nested-hooks | -24% | -61% | |
| entity-list | -22% | -45% | |
| rapid-dispatch | -21% | -65% | |
| multi-selector-component | -20% | -82% | |
| price-ticker | -19% | -58% | stock drops frames |
| selective-update | -15% | -64% | |
| forms | -12% | -80% | |
| entity-list-array | -9% | -62% | |
| many-components-same-slice | +3% | +96% | flat; work moved into dispatch |
| rtkq-separate-queries | +4% | +21% | |
| one-component-many-slices | +28% | +32% | 20,000 top-level keys |
| derived-selectors | +53% | +144% |
Render counts match 9.3.0 within noise, except where stock drops frames under load.
rtkq-separate-queries moved from +9% in alpha.1 to +4%. derived-selectors and one-component-many-slices remain the two scenarios where useSignalSelector is slower than useSelector. Their numbers vary a lot between runs, and before/after runs of this release's changes showed no measurable difference for either.
- Add prototype signals+path-tracking implementation by @markerikson in #2318
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.4.0-alpha.1...v9.4.0-alpha.2
v9.4.0-alpha.1
This alpha release fixes a bug where useSignalSelector stopped updating when a memoized Reselect selector was shared between components or called with the same state twice, changes how array methods called on tracked state return their elements, and adds a per-evaluation read cache plus a dev-mode warning for selectors that repeatedly read the same field in a loop. The stock Provider and useSelector are unchanged.
npm install react-redux@alpha
yarn add react-redux@alpha
pnpm add react-redux@alpha
This is still an alpha. Please try it out and give us feedback, especially on real apps using Reselect and entity adapters. The alpha.0 notes cover what useSignalSelector is and how to opt in.
After the alpha.0 release, we got feedback asking about two cases: reusing the same memoized selector instance across components, and conditional logic inside of a selector altering what fields it reads.
In alpha.0, every useSignalSelector evaluating against the same state object received the same tracking proxy. Reselect's argsMemoize (the weakMapMemoize default, and lruMemoize) keys its cache on argument identity, so the second evaluation with that proxy was a cache hit: the input selectors never ran, no state properties were read, and the hook recorded zero dependencies. That hook then never re-rendered.
This affected more than the reported case of two components sharing one createSelector. A single component with an inline wrapper (useSignalSelector(s => selectSummary(s).total)), a parametrized selector (selectScaled(s, factor)), nested memoized inputs, and components mounted after the cache was warm all went deaf after their first update.
Every tracked evaluation now gets a fresh root proxy. The consequence is that argsMemoize misses on every tracked run and the input selectors execute each time. The result function still memoizes on the input values, so derived objects keep stable identities and are shared across components as before. Hand-rolled caches that never read state (if (cached) return cached) still cannot be tracked, but those are equally stale under useSelector.
We also reviewed conditional selectors (s => s.toggle ? s.foo : s.bar). Turns out those already worked :) Dependencies are re-recorded from scratch on every evaluation, so switching branches drops the old branch and picks up the new one.
Once we fixed the cache bug and re-ran the benchmarks, we saw that the alpha.0 benchmark results were flawed. In particular, the derived-selectors benchmark had appeared to be a significant perf improvement over stock useSelector. However, with the cache fix in place, the correct behavior had more overhead. More hooks actually received updates, and result functions that iterated arrays of tracking proxies (selectAll, then .filter over the result) paid one proxy trap and one dependency registration per element read, per hook, per dispatch. Inside a Reselect result function that precision buys nothing, since the function re-runs on input identity anyway.
To address this, find, findLast, filter, slice, and now map return raw state objects instead of proxies, each with a single identity dependency on the element's path (items.{id:42} for arrays with a key field, items.3 otherwise). Because state is immutable, an identity dependency never misses a change to that element; it can only over-run when a field you did not read changes. Fields read off a returned element are not tracked separately. Direct index access (state.items[0].name) keeps field-level precision.
For map, callbacks that return the element itself, a primitive, or a proxy obtained elsewhere (ids.map(id => entities[id])) get precise dependencies. Callbacks that construct new objects fall back to one dependency on the whole array. forEach, reduce, and flatMap are still not overridden and depend on the whole array.
One side effect: elements returned from these methods compare correctly with === against references held outside the selector, without unwrap().
A selector like comments.filter(c => c.postId === post.id) reads post.id once per comment. When post is a tracking proxy, each read is a trap call. Within one evaluation that read is idempotent: the value cannot change and the dependency is already linked. Each proxy now remembers its last primitive read for the current evaluation and returns it directly on repeat.
In development, a selector that re-reads the same field 100+ times in a single evaluation logs a warning once, naming the path and suggesting hoisting the value into a local before the loop. Hoisting is still cheaper than the memo, but the memo recovers most of the difference without code changes.
Overall, the branch is still a significant perf win across most of our benchmark scenarios, and it now correctly handles additional common usage scenarios that we'd expect to see in real app code.
10 s per scenario, this release vs 9.3.0, Chrome, react-dom/profiling. "Script" is total scripting time; "blocked" is main-thread time inside dispatch().
| Scenario | Script | Blocked | Notes |
|---|---|---|---|
| tree-view | -55% | -76% | |
| entity-list-array | -49% | -85% | stock drops frames |
| many-components-many-slices | -37% | -56% | |
| deeptree-nested-hooks | -28% | -63% | |
| realistic-slice-count | -26% | -80% | |
| price-ticker | -21% | -59% | |
| forms | -20% | -81% | |
| multi-selector-component | -17% | -84% | |
| selective-update | -16% | -66% | |
| rapid-dispatch | -14% | -65% | |
| entity-list | +2% | -21% | noise |
| many-components-same-slice | +3% | +93% | flat; work moved into dispatch |
| rtkq-separate-queries | +9% | +25% | |
| one-component-many-slices | +19% | +22% | 20,000 top-level keys |
| derived-selectors | +36% | +119% | see below |
Render counts match 9.3.0 within noise on every scenario.
The alpha.0 notes reported derived-selectors at -87%. That number was wrong: 150 of the 200 hooks in that scenario were affected by the cache bug and never re-rendered after their first update. With the fix, every hook re-runs its input selectors through the tracking proxy on every relevant dispatch, and that is the cost shown.
one-component-many-slices improved from +36% in alpha.0 to +19%.
Bundle cost is unchanged from alpha.0: zero for apps that do not import useSignalSelector, about +7 kB min+gz for apps that do.
The useSignalSelector API page now covers array-method return values, the Reselect interaction, and the repeated-read behavior.
- Add prototype signals+path-tracking implementation by @markerikson in #2318
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.4.0-alpha.0...v9.4.0-alpha.1
v9.4.0-alpha.0
This alpha release adds an opt-in useSignalSelector hook and SignalProvider component that only re-run selectors whose state dependencies actually changed, plus a react-redux/signals entry point for adopting them app-wide, and restructures the hooks API docs into separate pages.
npm install react-redux@alpha
yarn add react-redux@alpha
pnpm add react-redux@alpha
This is an early alpha. The implementation is complete and heavily tested, but it has only been tried in a small number of real apps. Please try it out and give us feedback! We'd especially like to hear about performance differences after adopting useSignalSelector (render counts and dispatch timings), any differences or issues with selector behaviors (including edge cases in proxy wrapper access), and bundle sizes.
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. An app with 2,000 mounted useSelector hooks runs 2,000 selector functions per dispatch, then compares 2,000 results, even when the action touched one field. That work is usually cheap per selector but it scales linearly with app size and is the dominant per-dispatch cost in large apps.
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 leverage some of these techniques inside of React-Redux, invisibly to the end user.
useSignalSelector tracks which state paths each selector actually reads. Selectors run against a tracking proxy over the Redux state, which records property accesses like state.todos[3].text. On each dispatch, SignalProvider diffs the previous and next state trees and fires signals for the paths that changed. Only selectors that depend on a changed path re-run. Everything else is skipped entirely: no selector call, no equality check, no render.
Internal implementation details
Internally this is a path-keyed signal graph built on the `alien-signals` propagation algorithm. Signals are created lazily as selectors touch paths and are released when the last subscriber unmounts, so memory tracks what is mounted rather than the size of the state tree. Dependency tracking is two-tiered: at mount, a hook records only which top-level state keys it reads and registers cheaply against those. It builds its full deep dependency graph the first time one of those keys changes. Hooks that never see a relevant dispatch never pay for the deep graph.You can adopt this incrementally. SignalProvider is a drop-in replacement for Provider, and useSignalSelector has the same signature and options as useSelector and passes the entire existing useSelector test suite, so you can switch individual components:
import { SignalProvider, useSignalSelector } from 'react-redux'
function TodoText({ id }: { id: number }) {
const text = useSignalSelector((state) => state.todos.byId[id].text)
return <span>{text}</span>
}
Alternately, you can alias the whole app at once. The new react-redux/signals entry point exports SignalProvider as Provider and useSignalSelector as useSelector, so existing code and third-party libraries that import from react-redux pick up the new implementation without edits:
// vite.config.ts
export default defineConfig({
resolve: {
alias: { 'react-redux': 'react-redux/signals' },
},
})
connect, useDispatch, useStore, and stock useSelector continue to work unchanged inside a SignalProvider. Reselect selectors work as expected, including weakMapMemoize. Zombie-child and stale-props behavior matches useSelector. SSR produces identical markup.
We benchmarked the branch against 9.3.0 across 16 scenarios in our react-redux-benchmarks suite, 10 seconds each, using React DevTools profiling builds so render counts are exact.
Total script time improved in 13 of 16 scenarios. Time spent inside dispatch() dropped 49–88% in 12 of 16. Render counts matched 9.3.0 within a few percent everywhere except the scenarios where 9.3.0 was dropping frames under load, where signals renders more often because it keeps up. Some representative numbers:
| Scenario | Script | Time in dispatch() |
Avg dispatch | Renders |
|---|---|---|---|---|
| derived-selectors | -87% | -70% | 1.47 → 0.44 ms | 347 → 30 |
| tree-view | -55% | -73% | 9.1 → 2.3 ms | ≈ |
| multi-selector-component | -28% | -88% | 6.5 → 0.9 ms | ≈ |
| realistic-slice-count | -28% | -82% | 1.15 → 0.21 ms | 768 → 768 |
| many-components-many-slices | -30% | -49% | 8.1 → 3.7 ms | ≈ |
| price-ticker | -19% | -58% | 24.3 → 6.2 ms | 247 → 410 |
| forms | -20% | -80% | 0.92 → 0.18 ms | ≈ |
The gains come from skipping work: in derived-selectors, 9.3.0 re-runs every derived selector on each dispatch and renders 347 times; signals renders 30 times, and the extra renders in 9.3.0 were all wasted.
There are costs. A tracked selector evaluation is 3–4x slower than a raw one because it runs through a proxy, so a selector that does re-run costs more than before. Each dispatch pays a diff of the previous and next state; this is proportional to how much of the tree actually changed, since unchanged subtrees are skipped by reference. Mount is a few milliseconds slower on small apps and faster on large component trees. The one scenario that regresses is a single component whose selector reads thousands of top-level state keys (+36% script), where there is no precision to recover and the diff is pure overhead.
The biggest tradeoff is bundle size. Opting in costs about 22.7 kB minified / 7.2 kB min+gzip more (32.9 kB / 10.9 kB total), which includes the alien-signals runtime. This is an intentional tradeoff - the extra logic and internal complexity requires more code. Stick with the standard useSelector as a default - useSignalSelector is specifically meant for larger apps that will have a net benefit from its performance characteristics.
The good news is that apps that keep using Provider and useSelector pay nothing for this feature. Stock React-Redux in a Vite 8 build is 10.2 kB minified / 3.7 kB min+gzip, unchanged from 9.3.0, and we have a CI check confirming the signals code tree-shakes out. Note that the raw dist/ files grew a lot because the signals code ships in the same file; bundle-size bots that measure dist/ will report a large increase that does not reflect what your app ships.
Path tracking works by observing property reads and diffing plain data. That imposes rules that stock useSelector does not:
- State must be plain, immutably-updated objects and arrays. This is already what Redux requires, but
useSelectortolerates violations thatuseSignalSelectordoes not. If a reducer mutates an object in place and returns the same reference, the diff sees no change and dependent components will not update.Map,Set,Date, and class instances are tracked by reference only: replacing one triggers updates, mutating one does not. - Selectors must not mutate state. In development, writing to state inside a selector (
state.items.sort(),delete state.x,Object.assign(state.y, ...)) throws aTypeErrorwith a clear message. StockuseSelectorsilently allows this. Useslice().sort()ortoSorted(). - Selectors receive a proxy, not the raw state. Values you return that are objects are unwrapped automatically, so
useSignalSelectorresults are the same references stockuseSelectorwould return and are safe to use asuseEffectdeps orMapkeys. If you pass state objects out of the selector by some other route,unwrap()converts them back to raw state. useSignalSelector(state => state)never updates. Returning the root state (or otherwise reading nothing specific from it) gives the tracker nothing to depend on. Select the fields you need.- Array iteration tracks one level deep.
state.todos.map(t => t.text)depends on thetextcolumn across all todos, not on every property of every todo. Mapping and then reading deeper properties works, but may re-run more often than the minimal set.
Details for each are on the useSignalSelector and SignalProvider API pages.
The single "Hooks" API page has been split into separate pages for useSelector, useDispatch, and useStore, with the hooks overview page now serving as an index. New pages cover SignalProvider, useSignalSelector, and unwrap.
- Add prototype signals+path-tracking implementation by @markerikson in #2318
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.3.0...v9.4.0-alpha.0
v9.3.0
This feature release officially marks the connect API as deprecated.
That's it. That's the release. :)
Way back in 2022, I officially marked the original Redux core createStore method as deprecated in Redux 4.2.0. As I clearly stated in that release, the goal of marking createStore as deprecated was to encourage users to migrate to modern Redux Toolkit, especially for those users who don't read our docs (such as beginners following outdated tutorials or in bootcamps, etc). The change was visual-only - adding @deprecated just marks the import with a strikethrough in an IDE, and the docblock references the "use modern Redux Toolkit" docs page. No runtime errors, no behavior changes, just an indication that the function is considered obsolete and you shouldn't use it directly any more. I also exported a legacy_createStore alias - same function, no deprecation attribute.
It's 4 years later, and I'm finally doing the same thing for connect :)
Again, nothing about connect's behavior is changing, and we do not intend to remove the connect API. But it's 2026, and hooks are the correct way to use React and React-Redux today.
We do strongly encourage users to migrate from connect to the useSelector / useDispatch hooks in general. This should result in codebases that are easier to understand and ought to improve performance slightly due to the way updates are handled.
As with before, React-Redux now exports a legacy_connect alias that does not have the deprecation attribute applied.
We had set up trusted publishing for React-Redux a couple years ago and did some releases with that enabled, but at some point I did a follow-up release that still used the previous manual workflow, and that lost the trusted publishing provenance flag. We had recent issues requesting a new release with trusted publishing enabled again, so we've fixed that with this release.
- Mark
connectas deprecated by @markerikson in https://github.com/reduxjs/react-redux/pull/2269
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.2.0...v9.3.0
v9.2.0
This feature release updates the React peer dependency to work with React 19, and improves treeshakeability of our build artifacts.
React 19 was just released! We've updated our peer dep to accept React 19, and updated our runtime and type tests to check against both React 18 and 19.
Also see Redux Toolkit v2.5.0 for the same peer dep update.
We've done some nitty-gritty optimization work to ensure bundlers correctly treeshake unused parts of the bundle.
- Improve treeshakeability of build artifacts by @aryaemami59 in https://github.com/reduxjs/react-redux/pull/2176
- Migrate to React by @aryaemami59 in https://github.com/reduxjs/react-redux/pull/2172
- Migrate to React 19 (take 2) by @markerikson in https://github.com/reduxjs/react-redux/pull/2216
- Clean up devdeps by @markerikson in https://github.com/reduxjs/react-redux/pull/2217
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.1.2...v9.2.0
v9.1.2
This bugfix release removes the no-longer-necessary peer dependency on react-native, and tweaks a few TS types for compat with the upcoming React 19 release.
We've always had an awkward peer dependency on both ReactDOM and React Native, because of the need to import the unstable_batchedUpdates API directly from each reconciler. That's part of what led to the sequence of 9.x patch releases to deal with RN compat.
As of 9.0.3, we dropped the batching imports completely, since React 18 now batches by default. That means we didn't even have any remaining imports from react-native.
Meanwhile, React 18.3 just came out, but so did React Native 0.74. RN 0.74 still requires React 18.2.
This caused NPM users to have installation failures when trying to use React-Redux:
- React-Redux has a peer dep on RN
- RN has a peer dep on React 18.2
- But the latest React, 18.3 would get installed in the app
- NPM errors with a peer dep mismatch
We no longer need to list RN as a peer dep, and dropping that also fixes the NPM installation issues as well.
- Fix
useRefusages to be called with an explicit argument ofundefined. by @aryaemami59 in https://github.com/reduxjs/react-redux/pull/2164 - Replace usage of deprecated
JSXglobal namespace withReact.JSXby @aryaemami59 in https://github.com/reduxjs/react-redux/pull/2163 - Drop now-unneeded RN peer dep by @markerikson in https://github.com/reduxjs/react-redux/pull/2167
- Fix remaining React 19 types issues by @markerikson in https://github.com/reduxjs/react-redux/pull/2168
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.1.1...v9.1.2
v9.1.1
This bugfix release fixes an issue with connect and React Native caused by changes to our bundling setup in v9. Nested connect calls should work correctly now.
- Remove unused isProcessingDispatch by @Connormiha in https://github.com/reduxjs/react-redux/pull/2122
- Move
Equalsconstraint into an intersection type. by @DanielRosenwasser in https://github.com/reduxjs/react-redux/pull/2123 - Fix
useIsomorphicLayoutEffectusage in React Native environments by @aryaemami59 in https://github.com/reduxjs/react-redux/pull/2156
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.1.0...v9.1.1
v9.1.0
This minor release adds a new syntax for pre-typing hooks.
Previously, the approach for "pre-typing" hooks with your app settings was a little varied. The result would look something like the below:
import type { TypedUseSelectorHook } from "react-redux"
import { useDispatch, useSelector, useStore } from "react-redux"
import type { AppDispatch, AppStore, RootState } from "./store"
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppStore = useStore as () => AppStore
React Redux v9.1.0 adds a new .withTypes method to each of these hooks, analogous to the .withTypes method found on Redux Toolkit's createAsyncThunk.
The setup now becomes:
import { useDispatch, useSelector, useStore } from "react-redux"
import type { AppDispatch, AppStore, RootState } from "./store"
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppStore = useStore.withTypes<AppStore>()
- Update hooks.md — reselect usage with multiple instances simplified by @VorontsovIE in https://github.com/reduxjs/react-redux/pull/2110
- Modernize ESLint configuration by @aryaemami59 in https://github.com/reduxjs/react-redux/pull/2115
- Introduce pre-typed hooks via
hook.withTypes<RootState>()method by @aryaemami59 in https://github.com/reduxjs/react-redux/pull/2114
- @VorontsovIE made their first contribution in https://github.com/reduxjs/react-redux/pull/2110
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.0.4...v9.1.0
v9.0.4
This bugfix release updates the React Native peer dependency to be >= 0.69, to better reflect the need for React 18 compat and (hopefully) resolve issues with the npm package manager throwing peer dep errors on install.
- Allow react-native newer than 0.69 as peer dependency by @R3DST0RM in https://github.com/reduxjs/react-redux/pull/2107
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.0.3...v9.0.4
v9.0.3
This bugfix release drops the ReactDOM / React Native specific use of render batching, as React 18 now automatically batches, and updates the React types dependencies
React-Redux has long depended on React's unstable_batchedUpdates API to help batch renders queued by Redux updates. It also re-exported that method as a util named batch.
However, React 18 now auto-batches all queued renders in the same event loop tick, so unstable_batchedUpdates is effectively a no-op.
Using unstable_batchedUpdates has always been a pain point, because it's exported by the renderer package (ReactDOM or React Native), rather than the core react package. Our prior implementation relied on having separate batch.ts and batch.native.ts files in the codebase, and expecting React Native's bundler to find the right transpiled file at app build time. Now that we're pre-bundling artifacts in React-Redux v9, that approach has become a problem.
Given that React 18 already batches by default, there's no further need to continue using unstable_batchedUpdates internally, so we've removed our use of that and simplified the internals.
We still export a batch method, but it's effectively a no-op that just immediately runs the given callback, and we've marked it as @deprecated.
We've also updated the build artifacts and packaging, as there's no longer a need for an alternate-renderers entry point that omits batching, or a separate artifact that imports from "react-native".
- Drop renderer-specific batching behavior and deprecate
batchby @markerikson in https://github.com/reduxjs/react-redux/pull/2104 - Drop
@types/react-domand lower@types/reactto min needed by @markerikson in https://github.com/reduxjs/react-redux/pull/2105
Full Changelog: https://github.com/reduxjs/react-redux/compare/v9.0.2...v9.0.3