Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial experimental React 18 compat prototyping #1808

Merged
merged 17 commits into from Oct 3, 2021

Conversation

markerikson
Copy link
Contributor

@markerikson markerikson commented Sep 3, 2021

This PR:

  • Updates our React devDeps to 18.experimental versions, along with React Testing Library's PR build from feat: Use concurrent React when available testing-library/react-testing-library#937
  • Updates our TS config to use the types for those React builds
  • Fixes almost all of the failing tests, which were primarily due to state updates being triggered outside of act()
  • Actually swaps useSelector over to use useSyncExternalStore instead of our own subscription logic
  • Also updates connect to use useSyncExternalStore, albeit in a kinda sketchy way
  • Rips out the pure option for connect

@dai-shi has a proof-of-concept PR at pmndrs/zustand#550 that will likely be useful as a starting point.

Status

At this point I see 3 test failures. Two look like they're related to tests for "avoid setting state on unmount", and the other looks like it's a batching-related issue. Need to investigate those further.

Note that this so far involves no library code changes whatsoever. Once I get the current tests updated to be passing based on expected behavior with React 18 overall, the next step is to try switching our logic over to use useSyncExternalState per facebook/react#22211 , and see what happens.

I've just commented out / tweaked those three test failures for now so I can move on. I'll review them more later.

Update 1

I just successfully got useSelector migrated over to use useSyncExternalStore! I'm seeing one legit test failure atm - uSES is not correctly preserving the previous selected result when you give it a new selector reference, because of how it memoizes things.

I wrote some notes in facebook/react#22211 , and I'll paste those here:

  • React-Redux currently catches errors thrown from selectors inside of subscriptions, saves them in a ref, and forces a re-render. If the selector runs clean while rendering, no problem. If it throws again, we attach the original error's message to the new error just in case it was two different things that happened. However, uSES always swallows errors in checkIfSnapshotChanged, so we can no longer check the original error ourselves. I was able to work around this by having useSelector wrap the original selector with one that does a try/catch and saves errors to a ref, but wow is that hacky.
  • Is the "not a valid React 18 version" check accidentally inverted? It currently says if (React.startTransition !== undefined) {, which seems wrong. I'm currently running 18.0.0-alpha-1314299c7-20210901, which most definitely has a startTransition function defined, and I am seeing that warning printed.
  • Our test for "checks for updates when subscribing and re-renders if different" is seeing a third render occur, when it used to only see two, and I can't figure out what's causing that extra render to happen (especially since renders 2 and 3 are seeing the same selected state). My vague hypothesis is that it may have something to do with the test dispatching an action in a useLayoutEffect in a child, while the subscription happens in a useEffect in the hook in the parent?
  • The way that useSyncExternalStoreExtra's memoization check is implemented means that if you get a new selector function, there's no way for it to be equal to the prior result and reuse it, because all of the memoization variables are internal to the useMemo hook. Our v7 useSelector does this behavior by keeping around the last selected value in a useRef. We have a test for that in our codebase, and it's failing atm, and I'm pretty sure this is a "correct" failure based on what I'm seeing in the uSES implementation.

Update 2

I've just gotten connect updated as well, and I've also ripped out the pure option.

I'm not convinced that the way I integrated useSyncExternalStore into connect is at all "correct". connect is still doing all the actual child props derivation logic. It's really just deferring to uSES for the "trigger a re-render" step.

All of the "impure behavior" tests were failing with this approach. After thinking about it, this option seemed ripe for removal. It only existed because early React ecosystem usage sometimes still relied on external mutable sources that would be different every time the component re-rendered, so we always had to re-run mapState and the props merging logic. In today's world, that's no longer the case - external mutability is no longer a thing that shows up in codebases, and at this point if you're still doing that it's a bug.

I did run a SourceGraph search to try to find any places where people were actually using {pure: false}. Best I could come up with was https://sourcegraph.com/search?q=context:global+lang:JavaScript+connect%28.%2B%2C+.%2B%2C+.%2B%2C+%7B.*pure%5Cs%3F:%5Cs%3Ffalse.*%7D%29&patternType=regexp , which only had 59 hits in 1.2M repos. Of those 59 hits, half were FlowType typedefs. So, this seems safe to kill off, and it simplifies several other bits of the remaining codebase anyway.

At this point, there's two tests failing, and ironically they're both bugs in uSES itself:

  • It's not correctly memoizing and reusing the last returned value if it gets a new selector reference
  • It's printing a "no useLayoutEffect in SSR" warning

Now that I think about it, I do have some tests for useSelector that I'd temporarily tweaked and gotten to "pass" by changing the assertions just to let me keep going. I need to review those further.

@netlify
Copy link

netlify bot commented Sep 3, 2021

✔️ Deploy Preview for react-redux-docs ready!

🔨 Explore the source changes: 7c2b73f

🔍 Inspect the deploy log: https://app.netlify.com/sites/react-redux-docs/deploys/6159e838b482a30008d63bec

😎 Browse the preview: https://deploy-preview-1808--react-redux-docs.netlify.app

@codesandbox-ci
Copy link

codesandbox-ci bot commented Sep 3, 2021

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.

Latest deployment of this branch, based on commit 7c2b73f:

Sandbox Source
Vanilla Configuration
Vanilla Typescript Configuration

@markerikson markerikson marked this pull request as ready for review September 3, 2021 21:05
@markerikson
Copy link
Contributor Author

Hmm. Not sure why the tests aren't running. Created this as a "draft" initially and thought that might be why, but doesn't look like they're running after changing it to a normal PR either.

@timdorr
Copy link
Member

timdorr commented Sep 3, 2021

If you rebase off master, it should have a button for running tests now.

@markerikson markerikson force-pushed the feature/react-18-initial-compat branch from 0ed651a to a1fd23f Compare September 4, 2021 02:21
@github-actions
Copy link

github-actions bot commented Sep 4, 2021

Size Change: +3.38 kB (15%) ⚠️

Total Size: 22 kB

Filename Size Change
dist/react-redux.js 17.3 kB +3.12 kB (18%) ⚠️
dist/react-redux.min.js 4.73 kB +262 B (5%) 🔍

compressed-size-action

@eps1lon
Copy link
Contributor

eps1lon commented Sep 6, 2021

Is the "not a valid React 18 version" check accidentally inverted? It currently says if (React.startTransition !== undefined) {, which seems wrong. I'm currently running 18.0.0-alpha-1314299c7-20210901, which most definitely has a startTransition function defined, and I am seeing that warning printed.

I think this warning is intended since the shim of useSyncExternalStore does not support React 18. The warning should disappear in React 17 or once facebook/react#22239 lands.

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

It's not correctly memoizing and reusing the last returned value if it gets a new selector reference

This is an intentional part of the design of useSyncExternalStore — we always call getSnapshot during render, because it might have been changed by a parent. That's how we avoid zombie props.

The "extra" version of the API supports skipping the additional call during render... but only if you pass the same selector. So it has to be stable/memoized. (The difference compared to useMutableSource is that it won't de-opt to synchronous mode if it renders during a transition, and it won't resubscribe.)

Alternatively, if you'd rather use the zombie props (to preserve Redux's existing behavior, I'm guessing?), you might as well skip the "extra" version of the API and track the latest selector on a ref like you currently do, since our selector implementation won't provide any additional benefit over the lower-level API.

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

Aside from preserving existing behavior, though, I'd say that for the majority of cases it's fine not to memoize the selector. Especially if they don't pass a custom isEqual argument. Most selectors aren't that computationally expensive.

For those cases where the selectors are expensive, there's a straightforward solution: wrap it in useCallback. You can tell users to think of it as an optimization, similar to other scenarios in React where you'd use useMemo, memo, or PureComponent. But you wouldn't want to do this prematurely everywhere, because usually the impact of running the selector again is negligible compared to the weight of another hook.

This is going to be our advice for useSyncExternalStore in general.

@markerikson
Copy link
Contributor Author

@acdlite I'm not sure I explained the "memoized value vs new selector ref" issue enough here.

When a new selector reference gets passed into useSelector, yes, we do need to call the new selector because it's entirely possible that it could return something completely different than the old one did. (Granted, 99.9% of the time it's the exact some function code-wise, just inlined.) However, it's also possible (and even likely) that the value returned from the new selector passes the equality check vs the last returned value. In that case, we want to still reuse the old memoized value and return it, thus avoiding an extra unnecessary re-render.

What I'm seeing with the uSESExtra implementation atm is that because of how the memoization is implemented via a closure, it's impossible to get a new selector ref and still refer to the old result value to return it.

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

However, it's also possible (and even likely) that the value returned from the new selector passes the equality check vs the last returned value. In that case, we want to still reuse the old memoized value and return it, thus avoiding an extra unnecessary re-render.

Ah I see, ok I think it makes sense to support that

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

What would happen if you stripped down the implementation as much as possible? What features is React missing in the current proposal? (Other than not bailing out on equal selections when the selector changes, as discussed above, which we'll add.)

In other words, in what ways would the following implementation be insufficient:

function useSelector(selector, isEqual) {
  const store = useReduxStore();
  return useSyncExternalStoreExtra(
    store.subscribe,
    store.getState,
    selector,
    isEqual,
  );
}

Our goal with useSyncExternalStore is to upstream a lot of the complexity you're currently having to deal with in userspace, so whatever we're missing we'd love to hear about it.

I think the main extra things you currently have are:

  • a subscription manager that's aware of the tree structure
  • an error replay mechanism if a selector throws

My understanding is that both of these exist to account for the zombie children scenario. However, useSyncExternalStore is designed to avoid that, so I wonder which of your tests would break if you removed them and tried relying only on useSES?

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

an error replay mechanism if a selector throws

I think we mentioned this previously, but React actually uses this exact same mechanism for all errors, which we leverage in useSyncExternalStore. So I'm hoping you can rely on that instead of implementing it again yourself.

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

However, it's also possible (and even likely) that the value returned from the new selector passes the equality check vs the last returned value. In that case, we want to still reuse the old memoized value and return it, thus avoiding an extra unnecessary re-render.

So I remembered why I didn't implement it this way: React doesn't need the reference to be the same to skip re-rendering.

The way bailouts in React work is that, if a component re-renders but none of the state or props have changed, we assume the children are equal even if they fail an equality check.

So even if useSyncExternalStoreExtra returns a new reference, if no other state in the component has changed, then React will still bail out.

If you have a test that asserts the references are equal, then it will obviously fail, but if your test instead asserts that the children don't re-render, then it should pass.

If you find this still doesn't work as expected, could you point me to a failing test so I can take a look?

EDIT: Though I suppose it's still useful to return a memoized value even if the component does re-render, since it could preserve downstream memoizations. Ok disregard this comment, I'll go ahead and add this behavior.

@markerikson
Copy link
Contributor Author

Sounds good. And yeah, the currently failing test is at https://github.com/reduxjs/react-redux/pull/1808/files#diff-be963b0f28d22eab25f8f42209afbe9a4f5d16696f016853c310c7832303cdb5R621-R623 :

        it('reuse latest selected state on selector re-run', () => {
          const alwaysEqual = () => true
          const Comp = () => {
            // triggers render on store change
            useNormalSelector((s) => s.count)
            const array = useSelector(() => [1, 2, 3], alwaysEqual)
            renderedItems.push(array)
            useLayoutEffect(() => {
              renderedItems.push(array)
            })
            return <div />
          }

          rtl.render(
            <ProviderMock store={normalStore}>
              <Comp />
            </ProviderMock>
          )

          expect(renderedItems.length).toBe(1)

          normalStore.dispatch({ type: '' })
          act(() => {
            normalStore.dispatch({ type: '' })
          })

          expect(renderedItems.length).toBe(2)
          // TODO This is failing, and correctly so. `uSES` drops the memoized value if it gets a new selector.
          expect(renderedItems[0]).toBe(renderedItems[1])
        })

@markerikson
Copy link
Contributor Author

markerikson commented Sep 13, 2021

@acdlite I'll have to circle back to your other questions about "missing scenarios" later when I have more time to think about it.

You can see in this PR that I did some very stupid hacky things to get connect to continue working as-is - mostly the need to trigger those cascading subscriptions inside of the subscription listeners. Not sure there's anything applicable for uSES out of that, though.

For the error handling, useSelector currently tries to keep the original error from the subscriber around for reference, so that if it happens again while rendering, we can say "error Y may be related to previous error X". Tbh I'm not sure how fully useful that is, and falling back to uSES's own error handling may be acceptable here.

One other sorta-related train of thought. connect has long had an areStatesEqual option that bails out if the root state is considered the same. For some reason we never got around to adding that to useSelector. We recently did that in 7.2.5 with a simple if (prevState === currentState) return, and a user filed #1813 in response complaining that their app was breaking. Turns out it's some high-powered finance app that actually does really mutate the Redux state (!).

I mentioned that we could at least consider adding a similar areStatesEqual option to useSelector. Not sure if we'll actually end up doing that, but I don't think that would be feasible with uSES because it does its own is(prevState, currentState) check inside. I'm not saying that you should change that - just that it's a hypothetical scenario that uSES doesn't support.

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

Since this is not idiomatic Redux and you don't already support it, you could add that as a separate API?

@acdlite
Copy link
Contributor

acdlite commented Sep 13, 2021

Ok I fixed the memoization issue: facebook/react#22307.

Available in the experimental release channel, starting with use-sync-external-store@0.0.0-experimental-fd5e01c2e-20210913

Lemme know if it works as expected

@markerikson
Copy link
Contributor Author

The idea of an areStatesEqual comparison does have application beyond "we're being bad and mutating the state". For example, if you know that you're only ever reading from state.x, you could supply (prevState, newState) => prevState.x !== newState.x and avoid any execution of the selectors for this component until you know that state.x has changed. So, sort of a pre-optimization that could skip the "run the selector" stage entirely.

connect has had that option since v5. I'm not sold on adding it, but since the question did just come up in that other thread, it was just on my mind as a thing that I don't know if we could do with uSES as currently implemented. Maybe we could, if we did some getSnapshot shenanigans, but unlikely it would be worth the effort (especially given that we've gotten along without that option thus far).

@markerikson
Copy link
Contributor Author

markerikson commented Sep 14, 2021

@acdlite Just updated all our React devDep packages locally to 0.0.0-experimental-fd5e01c2e-20210913. Looks like that does pass the "return the last memoized value" test now.

I only see one other current test failure, which is the "no warnings from useLayoutEffect in SSR" test :)

but I do have a couple other render-count related tests I need to go back and review a bit.

Also, as I commented over in facebook/react#22307, the shim isn't looking for the unstable_ prefix, so as currently published it can never use the built-in version of uSES.

@markerikson
Copy link
Contributor Author

markerikson commented Sep 14, 2021

Huh. @acdlite , I clearly have issues understanding how the whole dispatcher thing works :)

unstable_useSyncExternalStore does not appear to be exported by react-dom@0.0.0-experimental-fd5e01ce-20210913. However, it seems like it is made available on the dispatcher, and if I hand-edit use-sync-external-store.development.js to refer to builtInAPI = React.unstable_useSyncExternalStore, and I add a logging line to the uSES function in react.development.js, I can confirm that the built-in API is being called.

In fact, when I do that, I actually see better test results in a way.

When I was doing the first work on this PR earlier, I saw three of our tests that do render counts in assertions start failing because of an extra render pass happening, and I couldn't figure out what was causing it.

If I do these hand-edits to force the use of the built-in uSES, I'm now seeing those tests revert back to the original (and expected) number of renders. Here's the "failures" with the built-in version that show the reversion to the original behavior:

  ● React › hooks › useSelector › lifecycle interactions › notices store updates between render and store subscription effect

    expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 0

      Array [
        0,
        1,
    -   1,
      ]

      238 |           // 2) Render due to dispatch, still sync in the initial render's commit phase
      239 |           // TODO 3) ??
    > 240 |           expect(renderedItems).toEqual([0, 1, 1])
          |                                 ^
      241 |         })
      242 |       })
      243 |

      at Object.<anonymous> (test/hooks/useSelector.spec.tsx:240:33)

  ● React › hooks › useSelector › performance optimizations and bail-outs › calls selector twice once on mount when state changes during render

    expect(received).toEqual(expected) // deep equality

    Expected: 3
    Received: 2

      427 |           // TODO As with "notice store updates" above, we're now getting _3_ renders here
      428 |           // expect(renderedItems.length).toEqual(2)
    > 429 |           expect(renderedItems.length).toEqual(3)
          |                                        ^
      430 |         })

So, it looks like there's a slight difference in behavior between the shim and the built-in implementation, which is not terribly surprising.

This is interesting. We should want to have our tests run against the built-in version, but I can't just hand-edit the shim file to make it work the way I want to as a real solution.

Also not sure how to handle the difference in render counts here. Will have to think about this a bit.

The one other bit of weirdness I'm seeing is with @testing-library/react-hooks. With my hand edits to use-sync-external-store.development.js, the two tests we have that actually call renderHooks() are exploding because they can't find the right function:

  ● React › hooks › useSelector › core subscription behavior › selects the state and renders the component when the store updates

    TypeError: dispatcher.useSyncExternalStore is not a function

      36 |   }, [store, contextSub])
      37 |
    > 38 |   return useSyncExternalStoreExtra(
         |          ^
      39 |     subscribe,
      40 |     store.getState,
      41 |     selector,

      at Object.useSyncExternalStore (node_modules/react/cjs/react.development.js:1626:21)
      at Object.useSyncExternalStoreExtra (node_modules/use-sync-external-store/cjs/use-sync-external-store-extra.development.js:112:36)
      at useSelectorWithStoreAndSubscription (src/hooks/useSelector.ts:38:10)
      at useSelector (src/hooks/useSelector.ts:84:27)
      at react_hooks_1.renderHook.wrapper.react_1.default.createElement (test/hooks/useSelector.spec.tsx:68:47)
      at TestHook (node_modules/@testing-library/react-hooks/lib/pure.js:51:14)
      at renderWithHooks (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6080:18)

I can only assume there's some shenanigans going on with import timing and Jest modules somehow? Perhaps it also has to do with React Hooks Testing Library not being updated yet.

If I change those two tests over to be normal function components + rtl.render(), instead of RHTL's renderHooks(), those two tests pass nicely with the built-in API.

@acdlite
Copy link
Contributor

acdlite commented Sep 14, 2021

I think the issue is just that useSyncExternalStore is still prefixed, even in the experimental channel, so the use-sync-external-store package doesn't pick up the native version yet. It always uses the shim.

The reason that matters is because the tests are running in a concurrent root (via React Testing Library*), but the shim is not designed to work in concurrent roots. It only works in legacy roots, where updates are always sync.

The idea is that the shim will always pick the native version in React 18, and always the shim in 17 and below, because useSyncExternalStore doesn't exist in anything before 18.

But the problem is, right now, the native API is prefixed with unstable_. So the package doesn't detect it, and falls back to the shim. That's why you get the warning, as @eps1lon explained here: #1808 (comment)

Until the useSyncExternalStore API is finalized, what I'll do is update the shim to import the unstable version. I should have done that from the beginning, didn't consider this in-between early testing phase.

In the meantime, you can continue your testing by downgrading React and React DOM back to 17.

(* I didn't realize React testing library automatically uses createRoot when it's available. We should ask them (and by "them" I guess I mean @eps1lon) not do that, since there are enough subtle batching changes that it's likely to break some existing tests if they all get automatically switched over when you upgrade React. But this is a perfect example of why we prefix things — people add feature testing for unstable APIs and then when we ship the stable it has the potential break their users. See also: remix-run/react-router#7308. OTOH if React Testing Library users are already using act everywhere then maybe it'll just work? Doesn't seem like that's ​the case, though.)

@acdlite
Copy link
Contributor

acdlite commented Sep 14, 2021

(I should have done that from the beginning, didn't consider this in-between early testing phase.)

Well, I kind of did — that's what the warning was for :D

@acdlite
Copy link
Contributor

acdlite commented Sep 14, 2021

Ok the prefix issue is fixed now, starting with 0.0.0-experimental-806aaa2e2-20210913

@acdlite
Copy link
Contributor

acdlite commented Sep 14, 2021

I can only assume there's some shenanigans going on with import timing and Jest modules somehow? Perhaps it also has to do with React Hooks Testing Library not being updated yet.

It looks like you updated the react and react-dom packages, but not react-test-renderer. The API is imported from the react package (which is why use-sync-external-store attempts to use it) but the renderer doesn't implement it. If you change the versions to match (either both 17 or both 18) then it will work.

@eps1lon
Copy link
Contributor

eps1lon commented Sep 14, 2021

(* I didn't realize React testing library automatically uses createRoot when it's available. We should ask them (and by "them" I guess I mean @eps1lon) not do that, since there are enough subtle batching changes that it's likely to break some existing tests if they all get automatically switched over when you upgrade React.

We specifically do this because you made it clear in your messaging that createRoot will be the default in React 18. It's also a bad impression to trigger warnings by default (ReactDOM.render is deprecated) in React 18.

I want to stress that warnings are very frustrating for library users in test suites since many suites are configured to fail if any warnings are triggered. So "it's just a warning" isn't helpful advise.

It's a bit odd now to hear that we should not default to createRoot when React itself considers it the default going forward. I'll see if more repositories report integration issues. Though from my impression doing this work in Material-UI we had relatively few places where act() was missing. But that test suite is also maintained by myself so I may have a bit of a bias towards how well act is integrated into existing React test suites. DOM related tests usually only break if they use imperative APIs such as click or focus. All the testing-library APIs are already wrapped in act. I suspect you use a bunch of setState in the tests that weren't wrapped in act before?

But the messaging is still a problem in my opinion. I don't have a strong opinion what should be the default going forward. But the ecosystem should agree on one, not many.

Edit: Next.js also uses createRoot by default if React 18 installed: https://nextjs.org/blog/next-11#upgrade-guide

@markerikson
Copy link
Contributor Author

Will respond in more detail on the morning, but quick thoughts :

  • yeah, I need to bump the renderer package
  • we've been assuming that v8 would require React 18 as a min peer dep, just like v7 required 16.8 for hooks. So, I'd want to stick with 18 for testing. That said, if 16/17 compat is viable, then A) we'd need to figure out a good way to run tests against each version, and B) the tests would have to account for some differences in behavior like batching. Like, I just saw earlier that the shim produces an extra render in some cases than the built-in api does.

@markerikson markerikson force-pushed the feature/react-18-initial-compat branch from dd019c8 to 8ec5802 Compare October 3, 2021 17:18
@markerikson markerikson force-pushed the feature/react-18-initial-compat branch from 8ec5802 to 7c2b73f Compare October 3, 2021 17:28
@markerikson
Copy link
Contributor Author

I rebased this against master, and squashed a bunch of temp commits together to clean up the history. I also fixed up some types issues. Tests pass, types pass, the CSB build looks like it runs and compiles okay in an example app. I think it's time to merge this PR and publish an initial v8 alpha so that people could start trying it out.

I still haven't tried to implement an actual API for server snapshots. I'll tackle the SSR thing in a follow-up.

I've updated the React peerDep to list 16 || 17 || 18 (although I don't know how to list "experimental" builds here). Per prior comments, I'm generally planning to make this 18-only by the time it's out.

Related to that, I'm seriously considering removing the use-sync-external-store shim dep, and inlining the logic from useSyncExternalStoreExtra back into useSelector. Otherwise, the logic from the useSyncExternalStore shim function ends up in the final app bundle even when you're using the built-in uSES function, and that's just a waste of bytes. If we're not going to support 16/17 via the shim in the final v8 release, then I don't see a point in having that dep at all.

Also, I had removed use of Subscription from useSelector, but that still ends up in the app bundle because Provider has to use it in case there's some connect usage in the app. So, can't get away from that.

@markerikson markerikson changed the title [DRAFT] Initial experimental React 18 compat prototyping Initial experimental React 18 compat prototyping Oct 3, 2021
@markerikson markerikson merged commit f101623 into master Oct 3, 2021
@markerikson markerikson deleted the feature/react-18-initial-compat branch October 3, 2021 17:50
@acdlite
Copy link
Contributor

acdlite commented Oct 3, 2021

@markerikson We could publish a version of the "extra" module that relies directly on the native API, instead of using the shim indirection. That way if multiple libraries depend on it, users only download the code once.

@markerikson
Copy link
Contributor Author

@acdlite yup, that'd definitely be another possibility, and I'd be good with that.

@markerikson
Copy link
Contributor Author

Also FYI to anyone reading, these changes are now live in https://github.com/reduxjs/react-redux/releases/tag/v8.0.0-alpha.0 !

@brainkim
Copy link

brainkim commented Oct 4, 2021

If we're not going to support 16/17 via the shim in the final v8 release, then I don't see a point in having that dep at all.

My understanding is that the shim will support post-hooks versions once the API is stable.

@markerikson
Copy link
Contributor Author

My point is that the "shim" part is only useful if we planned to have v8 be compatible with 16/17. But, I was already planning to enforce a hard switch so that React-Redux v8 requires React 18, in the same way that React-Redux v7 required 16.8+ for hooks support.

As currently written, the use-sync-external-store package will always end up including the shim code itself in the final app bundle, even if you're using a version of React that does have a real uSES. If we really do hard-require React 18, then that's a useless 750 bytes for end users that wouldn't even run.

@acdlite
Copy link
Contributor

acdlite commented Oct 31, 2021

As of facebook/react#22662, if you import use-sync-external-store/with-selector it will compose the built-in React API, not the shim. (The shim version is available as a separate import: use-sync-external-store/shim/with-selector.)

@Kasdy
Copy link

Kasdy commented Feb 4, 2022

Also, I had removed use of Subscription from useSelector

Hello @markerikson,
shouldn't this bring back the old quadratic algorithm and slow unmount of many components (#1523)?
Since Redux unsubscribe is passed to useSyncExternalStore and it relies on indexOf\splice (like in version <7.2.0)
Thank you

@markerikson
Copy link
Contributor Author

markerikson commented Feb 5, 2022

@Kasdy : not sure, but if there's a problem here it's probably going to be a React issue rather than a React-Redux issue specifically.

Could you try to set up a test case with both React-Redux v7 and React 17, React-Redux v8 + React 18, and also "just" React 18 + a fake little store implementation + useSyncExternalStore, and see what the actual behavior looks like?

edit

FWIW, after a quick glance I don't see any uses of indexOf or splice in React-Redux itself at this point. If you look at the useSyncExternalStore shim implementation there's no list of listeners there - it's just a useEffect directly returning whatever unsubscribe function we give it.

Hmm. However... because we're now using store.subscribe directly, that does use indexOf/splice.

Okay, yeah, this could be an issue.

Could you go ahead and set up those benchmarks anyway, just to confirm that that this is indeed a problem?

One more thought: <Provider> still relies on Subscription anyway, so it's going to end up in the bundle regardless. We probably should update useSelector to rely on that again.

@Kasdy
Copy link

Kasdy commented Feb 5, 2022

@markerikson

Hmm. However... because we're now using store.subscribe directly, that does use indexOf/splice.

Yes! that's what I meant :)

I adapted the benchmark code in #1523 to use Hooks and tested:

React 17 + React-Redux v7.2.6
Screenshot 2022-02-05 01 24 30

React 18 + React-Redux v8.0.0
Screenshot 2022-02-05 01 36 43

const store = createStore(
    (state = 0, action) => (action.type === "INC" ? state + 1 : state)
);
const increment = () => ({ type: "INC" });

function App() {
    const state = useSelector(s => s);
    const dispatch = useDispatch();

    const [children, setChildren] = useState(0);

    const toggleChildren = () => setChildren(c => c ? 0 : 50000);

    return <div>
        <button onClick={toggleChildren}>Toggle Children</button>
        <button onClick={() => dispatch(increment())}>Increment</button>
        {[...Array(children).keys()].map(i => <Child key={i}/>)}
    </div>;

}

function Child() {
    const v = useSelector(s => s);
    return <div className="child">{v}</div>;
}

@markerikson
Copy link
Contributor Author

markerikson commented Feb 5, 2022

Gotcha.

Could you file a new issue so we can keep track of this separately?

Also: I would really like to have a unit test that captures this intended behavior so that we don't regress again, but off the top of my head I don't know how you'd actually validate this experimentally (especially since perf-sensitive behavior is not a good fit for unit tests that run in C).

Also also: if you have time to file a PR that changes useSelector back to using Subscription again, that would be great!

github-merge-queue bot pushed a commit to valora-inc/wallet that referenced this pull request Mar 5, 2024
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [react-redux](https://togithub.com/reduxjs/react-redux) | [`^7.2.9` ->
`^9.1.0`](https://renovatebot.com/diffs/npm/react-redux/7.2.9/9.1.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/react-redux/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-redux/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-redux/7.2.9/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-redux/7.2.9/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>reduxjs/react-redux (react-redux)</summary>

###
[`v9.1.0`](https://togithub.com/reduxjs/react-redux/compare/v9.0.4...v9.1.0)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v9.0.4...v9.1.0)

###
[`v9.0.4`](https://togithub.com/reduxjs/react-redux/releases/tag/v9.0.4)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v9.0.3...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.

#### What's Changed

- Allow react-native newer than 0.69 as peer dependency by
[@&#8203;R3DST0RM](https://togithub.com/R3DST0RM) in
[reduxjs/react-redux#2107

**Full Changelog**:
reduxjs/react-redux@v9.0.3...v9.0.4

###
[`v9.0.3`](https://togithub.com/reduxjs/react-redux/releases/tag/v9.0.3)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v9.0.2...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

#### Changelog

##### Batching Dependency Updates

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"`.

#### What's Changed

- Drop renderer-specific batching behavior and deprecate `batch` by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2104
- Drop `@types/react-dom` and lower `@types/react` to min needed by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2105

**Full Changelog**:
reduxjs/react-redux@v9.0.2...v9.0.3

###
[`v9.0.2`](https://togithub.com/reduxjs/react-redux/releases/tag/v9.0.2)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v9.0.1...v9.0.2)

This **bugfix release** makes additional tweaks to the React Native
artifact filename to help resolve import and bundling issues with RN
projects.

#### What's Changed

- Change react-native output extension from `.mjs` to `.js` by
[@&#8203;aryaemami59](https://togithub.com/aryaemami59) in
[reduxjs/react-redux#2102

**Full Changelog**:
reduxjs/react-redux@v9.0.1...v9.0.2

###
[`v9.0.1`](https://togithub.com/reduxjs/react-redux/releases/tag/v9.0.1)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v9.0.0...v9.0.1)

This **bugfix release** updates the package to include a new
`react-redux.react-native.js` bundle that specifically imports React
Native, and consolidates all of the `'react'` imports into one file to
save on bundle size (and enable some tricky React Native import
handling).

##### What's Changed

- Add an RN-specific bundle and consolidate imports by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2100

**Full Changelog**:
reduxjs/react-redux@v9.0.0...v9.0.1

###
[`v9.0.0`](https://togithub.com/reduxjs/react-redux/releases/tag/v9.0.0)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.1.3...v9.0.0)

This **major release**:

-   Switches to requiring React 18 and Redux Toolkit 2.0 / Redux 5.0
- Updates the packaging for better ESM/CJS compatibility and modernizes
the build output
-   Updates the options for dev mode checks in `useSelector`
- Adds a new React Server Components artifact that throws on use, to
better indicate compat issues

This release has **breaking changes**.

This release is part of a wave of major versions of all the Redux
packages: **Redux Toolkit 2.0, Redux core 5.0, React-Redux 9.0, Reselect
5.0, and Redux Thunk 3.0**.

For full details on all of the breaking changes and other significant
changes to all of those packages, see the **["Migrating to RTK 2.0 and
Redux 5.0" migration
guide](https://redux.js.org/usage/migrations/migrating-rtk-2)** in the
Redux docs.

> \[!NOTE]
> The Redux core, Reselect, and Redux Thunk packages are included as
part of Redux Toolkit, and RTK users do not need to manually upgrade
them - you'll get them as part of the upgrade to RTK 2.0. (If you're not
using Redux Toolkit yet, [**please start migrating your existing legacy
Redux code to use Redux Toolkit
today!**](https://redux.js.org/usage/migrating-to-modern-redux))
> React-Redux is a separate, package, but we expect you'll be upgrading
them together.

```bash

##### React-Redux
npm install react-redux
yarn add react-redux

##### RTK
npm install @&#8203;reduxjs/toolkit
yarn add @&#8203;reduxjs/toolkit

##### Standalone Redux core
npm install redux
yarn add redux
```

##### Changelog

##### React 18 and RTK 2 / Redux core 5 Are Required

React-Redux 7.x and 8.x worked with all versions of React that had hooks
(16.8+, 17.x, 18.x). However, React-Redux v8 used React 18's new
`useSyncExternalStore` hook. In order to maintain backwards
compatibility with older React versions, we used the
`use-sync-external-store` "shim" package that provided an official
userland implementation of the `useSyncExternalStore` hook when used
with React 16 or 17. This meant that if you *were* using React 18, there
were a few hundred extra bytes of shim code being imported even though
it wasn't needed.

For React-Redux v9, we're switching so that **React 18 is now
*required*!** This both simplifies the maintenance burden on our side
(fewer versions of React to test against), and also lets us drop the
extra bytes because we can import `useSyncExternalStore` directly.

React 18 has been out for a year and a half, and other libraries like
React Query are also switching to require React 18 in their next major
version. This seems like a reasonable time to make that switch.

Similarly, React-Redux now depends on Redux core v5 for updated TS types
(but not runtime behavior). We strongly encourage all Redux users to be
using Redux Toolkit, which already includes the Redux core. Redux
Toolkit 2.0 comes with Redux core 5.0 built in.

##### ESM/CJS Package Compatibility

The biggest theme of the Redux v5 and RTK 2.0 releases is trying to get
"true" ESM package publishing compatibility in place, while still
supporting CJS in the published package.

**The primary build artifact is now an ESM file,
`dist/react-redux.mjs`**. Most build tools should pick this up. There's
also a CJS artifact, and a second copy of the ESM file named
`react-redux.legacy-esm.js` to support Webpack 4 (which does not
recognize the `exports` field in `package.json`). There's also two
special-case artifacts: an "alternate renderers" artifact that should be
used for any renderer other than ReactDOM or React Native (such as the
`ink` React CLI renderer), and a React Server Components artifact that
throws when any import is used (since using hooks or context would error
anyway in an RSC environment). Additionally, all of the build artifacts
now live under `./dist/` in the published package.

Previous releases actually shipped separate individual transpiled source
files - the build artifacts are now pre-bundled, same as the rest of the
Redux libraries.

##### Modernized Build Output

We now publish modern JS syntax targeting ES2020, including optional
chaining, object spread, and other modern syntax. If you need to

##### Build Tooling

We're now building the package using https://github.com/egoist/tsup. We
also now include sourcemaps for the ESM and CJS artifacts.

##### Dropping UMD Builds

Redux has always shipped with UMD build artifacts. These are primarily
meant for direct import as script tags, such as in a CodePen or a
no-bundler build environment.

We've dropped those build artifacts from the published package, on the
grounds that the use cases seem pretty rare today.

There's now a `react-redux.browser.mjs` file in the package that can be
loaded from a CDN like Unpkg.

If you have strong use cases for us continuing to include UMD build
artifacts, please let us know!

##### React Server Components Behavior

Per [Mark's post "My Experience Modernizing Packages to
ESM"](https://blog.isquaredsoftware.com/2023/08/esm-modernization-lessons/),
one of the recent pain points has been [the rollout of React Server
Components and the limits the Next.js + React teams have added to
RSCs](https://blog.isquaredsoftware.com/2023/08/esm-modernization-lessons/#problems-with-next-js-and-react-server-components).
We see many users try to import and use React-Redux APIs in React Server
Component files, then get confused why things aren't working right.

To address that, we've added a new entry point with a `"react-server"`
condition. Every export in that file will throw an error as soon as it's
called, to help catch this mistake earlier.

##### Dev Mode Checks Updated

In
[v8.1.0](https://togithub.com/reduxjs/react-redux/releases/tag/v8.1.0),
we updated `useSelector` to accept an options object containing options
to check for selectors that always calculate new values, or that always
return the root state.

We've renamed the `noopCheck` option to `identityFunctionCheck` for
clarity. We've also changed the structure of the options object to be:

```ts
export type DevModeCheckFrequency = 'never' | 'once' | 'always'

export interface UseSelectorOptions<Selected = unknown> {
  equalityFn?: EqualityFn<Selected>
  devModeChecks?: {
    stabilityCheck?: DevModeCheckFrequency
    identityFunctionCheck?: DevModeCheckFrequency
  }
}
```

##### `hoist-non-react-statics` and `react-is` Deps Inlined

Higher Order Components have been discouraged in the React ecosystem
over the last few years. However, we still include the `connect` API.
It's now in maintenance mode and not in active development.

As described in [the React legacy docs on
HOCs](https://legacy.reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over),
one quirk of HOCs is needing to copy over static methods to the wrapper
component. The `hoist-non-react-statics` package has been the standard
tool to do that.

We've inlined a copy of `hoist-non-react-statics` and removed the
package dep, and confirmed that this improves tree-shaking.

We've also done the same with the `react-is` package as well, which was
also only used by `connect`.

This should have no user-facing effects.

##### TypeScript Support

We've dropped support for TS 4.6 and earlier, and our support matrix is
now TS 4.7+.

##### What's Changed

- Update packaging, build config, and entry points for v9 by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2038
- Add stack to dev mode checks by
[@&#8203;EskiMojo14](https://togithub.com/EskiMojo14) in
[reduxjs/react-redux#2064
- add an extra entrypoint for React Server Components by
[@&#8203;phryneas](https://togithub.com/phryneas) in
[reduxjs/react-redux#2062
- Inline hoist-non-react-statics to eliminate a dep and help shaking by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2066
- Make context typing more accurate by
[@&#8203;EskiMojo14](https://togithub.com/EskiMojo14) in
[reduxjs/react-redux#2041
- Fix `uSES` imports and run against RTK CI examples by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2070
- Copy CI setup for RTK examples by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2072
- Fix useSelector() in combination with lazy loaded components breaks
with react v18
([#&#8203;1977](https://togithub.com/reduxjs/react-redux/issues/1977))
by [@&#8203;jeroenpx](https://togithub.com/jeroenpx) in
[reduxjs/react-redux#2068
- Actually add `sideEffects: "false"` to `package.json` in v9 by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2079
- Inline `react-is` utils to fix tree-shaking in 9.0 by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2085
- Rename `noopCheck` to `identityFunctionCheck` by
[@&#8203;aryaemami59](https://togithub.com/aryaemami59) in
[reduxjs/react-redux#2091
- Use scoped JSX for React types by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2092

**Full Changelog**:
reduxjs/react-redux@v8.1.2...v9.0.0

###
[`v8.1.3`](https://togithub.com/reduxjs/react-redux/compare/v8.1.2...v8.1.3)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.1.2...v8.1.3)

###
[`v8.1.2`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.1.2)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.1.1...v8.1.2)

This version changes imports from the React package to namespace imports
so the package can safely be imported in React Server Components as long
as you don't actually use it - this is for example important if you want
to use the React-specifc `createApi` function from Redux Toolkit.

Some other changes:

- The behaviour of the "React Context Singletons" from 8.1.1 has been
adjusted to also work if you have multiple React instances of the same
version (those will now be separated) and if you are in an environment
without `globalThis` (in this case it will fall back to the previous
behaviour).
- We do no longer use Proxies, which should help with some very outdated
consumers, e.g. smart TVs, that cannot even polyfill Proxies.

**Full Changelog**:
reduxjs/react-redux@v8.1.1...v8.1.2

###
[`v8.1.1`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.1.1)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.1.0...v8.1.1)

This bugfix release tweaks the recent lazy context setup logic to ensure
a single React context instance per React version, and removes the
recently added RTK peerdep to fix an issue with Yarn workspaces.

#### Changelog

##### React Context Singletons

React Context has always relied on reference identity. If you have two
different copies of React or a library in a page, that can cause
multiple versions of a context instance to be created, leading to
problems like the infamous "Could not find react-redux context" error.

In
[v8.1.0](https://togithub.com/reduxjs/react-redux/releases/tag/v8.1.0),
we reworked the internals to lazily create our single
`ReactReduxContext` instance to avoid issues in a React Server
Components environment.

This release further tweaks that to stash a single context instance per
React version found in the page, thus hopefully avoiding the "multiple
copies of the same context" error in the future.

#### What's Changed

- fix: fix typescript error on non exported type by
[@&#8203;luzzif](https://togithub.com/luzzif) in
[reduxjs/react-redux#2034
- create singleton context by React version by
[@&#8203;phryneas](https://togithub.com/phryneas) in
[reduxjs/react-redux#2039
- remove RTK peerDep by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[`44fc725`](https://togithub.com/reduxjs/react-redux/commit/44fc725)

**Full Changelog**:
reduxjs/react-redux@v8.1.0...v8.1.1

###
[`v8.1.0`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.1.0)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.7...v8.1.0)

This **feature release** adds new development-mode safety checks for
common errors (like poorly-written selectors), adds a workaround to fix
crash errors when React-Redux hooks are imported into React Server
Component files, and updates our hooks API docs page with improved
explanations and updated links.

#### Changelog

##### Development Mode Checks for `useSelector`

We've had a number of users tell us over time that it's common to
accidentally write selectors that have bad behavior and cause
performance issues. The most common causes of this are either selectors
that unconditionally return a new reference (such as `state =>
state.todos.map()` without any memoization ), or selectors that actually
return the *entire* root state ( `state => state` ).

We've updated `useSelector` to add safety checks in development mode
that warn if these incorrect behaviors are detected:

- Selectors will be called twice with the same inputs, and `useSelector`
will warn if the results are different references
- `useSelector` will warn if the selector result is actually the entire
root `state`

By default, **these checks only run *once* the first time `useSelector`
is called**. This should provide a good balance between detecting
possible issues, and keeping development mode execution performant
without adding many unnecessary extra selector calls.

If you want, you can configure this behavior globally by passing the
enum flags directly to `<Provider>`, or on a per-`useSelector` basis by
passing an options object as the second argument:

```ts
// Example: globally configure the root state "noop" check to run every time
<Provider store={store} noopCheck="always">
  {children}
</Provider>
```

```ts
// Example: configure `useSelector` to specifically run the reference checks differently:
function Component() {
  // Disable check entirely for this selector
  const count = useSelector(selectCount, { stabilityCheck: 'never' })
  // run once (default)
  const user = useSelector(selectUser, { stabilityCheck: 'once' })
  // ...
}
```

This goes along with the similar safety checks we've added to [Reselect
v5
alpha](https://togithub.com/reduxjs/reselect/releases/tag/v5.0.0-alpha.2)
as well.

##### Context Changes

We're still trying to work out how to properly use Redux and React
Server Components together. One possibility is using RTK Query's
`createApi` to define data fetching endpoints, and using the generated
thunks to fetch data in RSCs, but it's still an open question.

However, users have reported that merely importing *any* React-Redux API
in an RSC file causes a crash, because `React.createContext` is not
defined in RSC files. RTKQ's React-specific `createApi` entry point
imports React-Redux, so it's been unusable in RSCs.

This release adds a workaround to fix that issue, by using a proxy
wrapper around our singleton `ReactReduxContext` instance and lazily
creating that instance on demand. In testing, this appears to both
continue to work in all unit tests, *and* fixes the import error in an
RSC environment. We'd appreciate further feedback in case this change
does cause any issues for anyone!

We've also tweaked the internals of the hooks to do checks for correct
`<Provider>` usage when using a custom context, same as the default
context checks.

##### Docs Updates

We've cleaned up some of the Hooks API reference page, and updated links
to the React docs.

#### What's Changed

- check for Provider even when using custom context by
[@&#8203;EskiMojo14](https://togithub.com/EskiMojo14) in
[reduxjs/react-redux#1990
- Add a stability check, to see if selector returns stable result when
called with same parameters. by
[@&#8203;EskiMojo14](https://togithub.com/EskiMojo14) in
[reduxjs/react-redux#2000
- Add an E2E-ish test that verifies behavior when imported into RSCs by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2030
- lazily create Context for RSC compat by
[@&#8203;phryneas](https://togithub.com/phryneas) in
[reduxjs/react-redux#2025
- Add warning for selectors that return the entire state by
[@&#8203;EskiMojo14](https://togithub.com/EskiMojo14) in
[reduxjs/react-redux#2022

**Full Changelog**:
reduxjs/react-redux@v8.0.7...v8.1.0

###
[`v8.0.7`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.7)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.6...v8.0.7)

This release updates the peer dependencies to accept Redux Toolkit, and
accept the ongoing RTK and Redux core betas as valid peer deps.

> **Note**: These changes were initially in 8.0.6, but that had a typo
in the peer deps that broke installation. Sorry!

#### What's Changed

- Bump Redux peer deps to accept 5.0 betas, and bump RTK dev dep by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2017
- [`d45204f`](https://togithub.com/reduxjs/react-redux/commit/d45204f) :
Fix broken RTK peer dep

**Full Changelog**:
reduxjs/react-redux@v8.0.5...v8.0.7

###
[`v8.0.6`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.6)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.5...v8.0.6)

\~~This release updates the peer dependencies to accept Redux Toolkit,
and accept the ongoing RTK and Redux core betas as valid peer deps.~~

**This release has a peer deps typo that breaks installation - please
use 8.0.7 instead !**

#### What's Changed

- Bump Redux peer deps to accept 5.0 betas, and bump RTK dev dep by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#2017

**Full Changelog**:
reduxjs/react-redux@v8.0.5...v8.0.6

###
[`v8.0.5`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.5)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.4...v8.0.5)

This release fixes a few minor TS issues.

#### What's Changed

- `Provider`: pass state (`S`) generic through to `ProviderProps` by
[@&#8203;OliverJAsh](https://togithub.com/OliverJAsh) in
[reduxjs/react-redux#1960
- wrap `equalityFn` type in `NoInfer` by
[@&#8203;phryneas](https://togithub.com/phryneas) in
[reduxjs/react-redux#1965
- Fix wrapped component prop types when passing nullish
mapDispatchToProps by
[@&#8203;marconi1992](https://togithub.com/marconi1992) in
[reduxjs/react-redux#1928

**Full Changelog**:
reduxjs/react-redux@v8.0.4...v8.0.5

###
[`v8.0.4`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.4)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.3...v8.0.4)

This patch release fixes some minor TS types issues, and updates the
rarely-used `areStatesEqual` option for `connect` to now pass through
`ownProps` for additional use in determining which pieces of state to
compare if desired.

> **Note**: 8.0.3 was accidentally published without one of these fixes.
Use 8.0.4 instead.

#### Changelog

##### TS Fixes

We've fixed an import of `React` that caused issues with the
`allowSyntheticDefaultImports` TS compiler flag in user projects.

`connect` already accepted a custom context instance as `props.context`,
and had runtime checks in case users were passing through a real value
with app data as `props.context` instead. However, the TS types did not
handle that case, and this would fail to compile. If your own component
expects `props.context` with actual data, `connect`'s types now use that
type instead.

The `ConnectedProps<T>` type had a mismatch with React's built-in
`React.ComponentProps<Component>` type, and that should now work
correctly.

##### Other Changes

The `areStatesEqual` option to `connect` now receives `ownProps` as
well, in case you need to make a more specific comparison with certain
sections of state.

The new signature is:

```ts
{
  areStatesEqual?: (
    nextState: State,
    prevState: State,
    nextOwnProps: TOwnProps,
    prevOwnProps: TOwnProps
  ) => boolean
}
```

#### What's Changed

- Don't require allowSyntheticDefaultImports: true by
[@&#8203;apepper](https://togithub.com/apepper) in
[reduxjs/react-redux#1924
- Fixed type issue with `ComponentProps` from older `@types/react` by
[@&#8203;Andarist](https://togithub.com/Andarist) in
[reduxjs/react-redux#1956
- connect: pass ownProps to areStatesEqual by
[@&#8203;jspurlin](https://togithub.com/jspurlin) in
[reduxjs/react-redux#1951
- Omit built-in context prop if user component props include context by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1958

**Full Changelog**:
reduxjs/react-redux@v8.0.2...v8.0.4

###
[`v8.0.3`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.3)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.2...v8.0.3)

**This release was accidentally published without an intended fix -
please use
[v8.0.4](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.4)
instead**

###
[`v8.0.2`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.2)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.1...v8.0.2)

This patch release tweaks the behavior of `connect` to print a one-time
warning when the obsolete `pure` option is passed in, rather than
throwing an error. This fixes crashes caused by libraries such as
`react-beautiful-dnd` continuing to pass in that option (unnecessarily)
to React-Redux v8.

#### What's Changed

- Show warning instead of throwing error that pure option has been
removed by [@&#8203;ApacheEx](https://togithub.com/ApacheEx) in
[reduxjs/react-redux#1922

**Full Changelog**:
reduxjs/react-redux@v8.0.1...v8.0.2

###
[`v8.0.1`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.1)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v8.0.0...v8.0.1)

This release fixes an incorrect internal import of our `Subscription`
type, which was causing TS compilation errors in some user projects.
We've also listed `@types/react-dom` as an optional peerDep. There are
no runtime changes in this release.

#### What's Changed

- Add optional peer dependency on
[@&#8203;types/react-dom](https://togithub.com/types/react-dom) by
[@&#8203;Methuselah96](https://togithub.com/Methuselah96) in
[reduxjs/react-redux#1904
- fix(ts): incorrect import of `Subscription` causes `noImplicitAny`
error by [@&#8203;vicrep](https://togithub.com/vicrep) in
[reduxjs/react-redux#1910

**Full Changelog**:
reduxjs/react-redux@v8.0.0...v8.0.1

###
[`v8.0.0`](https://togithub.com/reduxjs/react-redux/releases/tag/v8.0.0)

[Compare
Source](https://togithub.com/reduxjs/react-redux/compare/v7.2.9...v8.0.0)

This **major version** release updates `useSelector`, `connect`, and
`<Provider>` for compatibility with React 18, rewrites the React-Redux
codebase to TypeScript (obsoleting use of `@types/react-redux`),
modernizes build output, and removes the deprecated `connectAdvanced`
API and the `pure` option for `connect`.

    npm i react-redux@latest

    yarn add react-redux@latest

#### Overview, Compatibility, and Migration

Our public API is still the same ( `<Provider>`, `connect` and
`useSelector/useDispatch`), but we've updated the internals to use the
new `useSyncExternalStore` hook from React. React-Redux v8 is still
compatible with all versions of React that have hooks (16.8+, 17.x, and
18.x; React Native 0.59+), and *should* just work out of the box.

In most cases, it's very likely that the only change you will need to
make is bumping the package version to `"react-redux": "^8.0"`.

*If* you are using the rarely-used `connectAdvanced` API, you will need
to rewrite your code to avoid that, likely by using the hooks API
instead. Similarly, the `pure` option for `connect` has been removed.

If you are using Typescript, React-Redux is now written in TS and
includes its own types. You should remove any dependencies on
`@types/react-redux`.

While not directly tied to React-Redux, note that **the recently updated
`@types/react@18` major version has changed component definitions to
remove having `children` as a prop by default**. This causes errors if
you have multiple copies of `@types/react` in your project. To fix this,
tell your package manager to resolve `@types/react` to a single version.
Details:

[**React issue #&#8203;24304: React 18 types broken since
release**](https://togithub.com/facebook/react/issues/24304#issuecomment-1094565891)

Additionally, please see the React post on [**How to Ugprade to React
18**](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html)
for details on how to migrate existing apps to correctly use React 18
and take advantage of its new features.

#### Changelog

##### React 18 Compatibility

React-Redux now requires the new [`useSyncExternalStore` API in React
18](https://togithub.com/reactwg/react-18/discussions/86). By default,
it uses the "shim" package which backfills that API in earlier React
versions, so **React-Redux v8 is compatible with all React versions that
have hooks** (16.8+, and React Native 0.59+) as its acceptable peer
dependencies.

We'd especially like to thank the React team for their extensive support
and cooperation during the `useSyncExternalStore` development effort.
They specifically designed `useSyncExternalStore` to support the needs
and use cases of React-Redux, and we used React-Redux v8 as a testbed
for how `useSyncExternalStore` would behave and what it needed to cover.
This in turn helped ensure that `useSyncExternalStore` would be useful
and work correctly for other libraries in the ecosystem as well.

Our performance benchmarks show parity with React-Redux v7.2.5 for both
`connect` and `useSelector`, so we do not anticipate any meaningful
performance regressions.

##### `useSyncExternalStore` and Bundling

The `useSyncExternalStore` shim is imported directly in the main entry
point, so it's *always* included in bundles even if you're using React
18. This adds roughly 600 bytes minified to your bundle size.

If you are using React 18 and would like to avoid that extra bundle
cost, React-Redux now has a new `/next` entry point. This exports the
exact same APIs, but directly imports `useSyncExternalStore` from React
itself, and thus avoids including the shim. You can alias
`"react-redux": "react-redux/next"` in your bundler to use that instead.

##### SSR and Hydration

React 18 introduces a new `hydrateRoot` method for hydrating the UI on
the client in Server-Side Rendering usage. As part of that, the
`useSyncExternalStore` API requires that we pass in an alternate state
value other than what's in the actual Redux store, and that alternate
value will be used for the entire initial hydration render to ensure the
initial rehydrated UI is an exact match for what was rendered on the
server. After the hydration render is complete, React will then apply
any additional changes from the store state in a follow-up render.

React-Redux v8 supports this by adding a new `serverState` prop for
`<Provider>`. If you're using SSR, you should pass your serialized state
to `<Provider>` to ensure there are no hydration mismatch errors:

```ts
import { hydrateRoot } from 'react-dom/client'
import { configureStore } from '@&#8203;reduxjs/toolkit'
import { Provider } from 'react-redux'

const preloadedState = window.__PRELOADED_STATE__

const clientStore = configureStore({
  reducer: rootReducer,
  preloadedState,
})

hydrateRoot(
  document.getElementById('root'),
  <Provider store={clientStore} serverState={preloadedState}>
    <App />
  </Provider>
)
```

##### TypeScript Migration and Support

The React-Redux library source has always been written in plain JS, and
the community maintained the TS typings separately as
`@types/react-redux`.

We've (finally!) [migrated the React-Redux codebase to
TypeScript](https://togithub.com/reduxjs/react-redux/issues/1737), using
the existing typings as a starting point. This means that **the
`@types/react-redux` package is no longer needed, and you should remove
that as a dependency**.

> **Note** Please ensure that any installed copies of `redux` and
`@types/react` are de-duped. You are also encouraged to update to the
latest versions of Redux Toolkit (1.8.1+) or Redux (4.1.2), to ensure
consistency between installed types and avoid problems from types
mismatches.

We've tried to maintain the same external type signatures as much as
possible. If you do see any compile problems, please file issues with
any apparent TS-related problems so we can review them.

The TS migration was a great collaborative effort, with many community
members contributing migrated files. Thank you to everyone who helped
out!

In addition to the "pre-typed" `TypedUseSelectorHook`, there's now also
a `Connect<State = unknown>` type that can be used as a "pre-typed"
version of `connect` as well.

As part of the process, we also updated the repo to use Yarn 3, copied
the typetests files from DefinitelyTyped and expanded them, and improved
our CI setup to test against multiple TS versions.

##### Removal of the `DefaultRootState` type

The `@types/react-redux` package, which has always been maintained by
the community, included a `DefaultRootState` interface that was intended
for use with TS's "module augmentation" capability. Both `connect` and
`useSelector` used this as a fallback if no state generic was provided.
When we migrated React-Redux to TS, we copied over all of the types from
that package as a starting point.

However, the Redux team [specifically considers use of a globally
augmented state type to be an
anti-pattern](https://togithub.com/reduxjs/react-redux/issues/1879).
Instead, we direct users to [extract the `RootState` and `AppDispatch`
types from the store
setup](https://redux.js.org/tutorials/typescript-quick-start#define-root-state-and-dispatch-types),
and [create pre-typed versions of the React-Redux
hooks](https://redux.js.org/tutorials/typescript-quick-start#define-typed-hooks)
for use in the app.

Now that React-Redux itself is written in TS, we've opted to remove the
`DefaultRootState` type entirely. State generics now default to
`unknown` instead.

Technically [the module augmentation approach can still be done in
userland](https://togithub.com/reduxjs/react-redux/issues/1879#issuecomment-1073284804),
but we discourage this practice.

##### Modernized Build Output

We've always targeted ES5 syntax in our published build artifacts as the
lowest common denominator. Even the "ES module" artifacts with
`import/export` keywords still were compiled to ES5 syntax otherwise.

With IE11 now effectively dead and many sites no longer supporting it,
we've updated our build tooling to target a more modern syntax
equivalent to ES2017, which shrinks the bundle size slightly.

If you still need to support ES5-only environments, please compile your
own dependencies as needed for your target environment.

##### Removal of Legacy APIs

We announced in 2019 that [the legacy `connectAdvanced` API would be
removed in the next major
version](https://togithub.com/reduxjs/react-redux/issues/1236), as it
was rarely used, added internal complexity, and was also basically
irrelevant with the introduction of hooks. As promised, we've removed
that API.

We've also removed the `pure` option for `connect`, which forced
components to re-render regardless of whether props/state had actually
changed if it was set to `false`. This option was needed in some cases
in the early days of the React ecosystem, when components sometimes
relied on external mutable data sources that could change outside of
rendering. Today, no one writes components that way, the option was
barely used, and React 18's `useSyncExternalStore` strictly requires
immutable updates. So, we've removed the `pure` flag.

Given that both of these options were almost never used, this shouldn't
meaningfully affect anyone.

#### Changes

Due to the TS migration effort and number of contributors, this list
covers just the major changes:

- Integrate TypeScript port by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1739
- Initial experimental React 18 compat prototyping by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1808
- Fix compatibility with React 18 strict effects by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1817
- Update to latest React 18 alpha dependencies by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1834
- Port remaining v7 typetests and improve v8 types by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1855
- Add initial SSR support for React 18 and React-Redux v8 by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1835
- test: Adjust type tests to be compatible with React 18 typings by
[@&#8203;eps1lon](https://togithub.com/eps1lon) in
[reduxjs/react-redux#1868
- Switch back to Subscription in useSelector to fix unsubscribe perf by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1870
- Cleanup more code after `pure` removal by
[@&#8203;Andarist](https://togithub.com/Andarist) in
[reduxjs/react-redux#1859
- Swap `useSyncExternalStore` shim behavior and update React deps by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1884
- Remove `DefaultRootState` type by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1887
- Add SSR test for `serverState` behavior by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1888
- Cleanup internal types in selectorFactory.ts by
[@&#8203;Methuselah96](https://togithub.com/Methuselah96) in
[reduxjs/react-redux#1889
- Remove ts-ignore for initMergeProps by
[@&#8203;Methuselah96](https://togithub.com/Methuselah96) in
[reduxjs/react-redux#1891
- fix(deps): add optional peer deps into `peerDependencies` by
[@&#8203;kyletsang](https://togithub.com/kyletsang) in
[reduxjs/react-redux#1893
- Update peer deps for v8 by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1895
- Port DT fix for `dispatchProp` arg in `mergeProps` by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1897
- Update docs for v8 final by
[@&#8203;markerikson](https://togithub.com/markerikson) in
[reduxjs/react-redux#1902

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 5pm,every weekend" in timezone
America/Los_Angeles, Automerge - "after 5pm,every weekend" in timezone
America/Los_Angeles.

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/valora-inc/wallet).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4yMjAuMiIsInVwZGF0ZWRJblZlciI6IjM3LjIyMC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jean Regisser <jean.regisser@gmail.com>
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.

None yet

7 participants