Skip to content

Commit 40b8aed

Browse files
Migrate to React 19 (#4409)
* Update `react-redux` to 9.1.2 * Run tests against different versions of React during CI * Remove unnecessary `tick` and `runAllTimers` from `cleanup.test.tsx` * Remove redundant `act` wrappers around `fireEvent` method calls - According to latest documentation of `@testing-library/react`, `fireEvent` methods are already wrapped inside `act` calls making manual wrapping unnecessary. * Workaround issue `userEvent` not working with fake timers - There currently seems to be an issue involving `sinon` fake timers used by `vitest`, `@testing-library/react` only supporting `jest` fake timers and `@testing-library/user-event` using `setTimeout` internally to simulate user actions such as button presses. Currently `@testing-library/react` only works with `jest` fake timers, which means if there are any component updates while `sinon` fake timers are running in `vitest` , `@testing-library/react` will not catch it and things start to break. - To workaround this issue, We have to setup the `user` by calling `userEvent.setup({ delay: null })`. The reason why We do this is because `@testing-library/user-event` uses `setTimeout` internally which cannot be awaited in a test while fake timers are running as it can cause the tests to indefinitely hang. So the current workaround is to disable the `delay` functionality of `userEvent` and prevent it from calling `setTimeout`. We also have to pass in `shouldAdvanceTime: true` to `vi.useFakeTimers()` as it can get around the issue of `@testing-library/react` not tracking `sinon` fake timers in `vitest`. * Fix test names in `fork.test.ts` - Fixed test names in `fork.test.ts` which could cause the terminal to flicker in Windows. * Fix issue with `console` spy inside `buildHooks.test.tsx` - `.mockReset()` should not be called in spies since it calls `.mockClear()` and returns the implementation to its **initial** form. In this case it was silencing some of the `act` related warnings emitted by `@testing-library/react` which needed to be resolved since they were calling issues. So `.mockReset()` calls on spies need to be changed to `.mockRestore()` calls since `.mockRestore()` restores the implementation to its **original** form. * Fix `act` related issues in `buildHooks.test.tsx` * Fix issues related to spies and stubbing environments in `utils.spec.ts` * Fix `console` spy related issues in `effectScenarios.test.ts` * Fix `console` spy related issues in `listenerMiddleware.test.ts` * Fix `console` spy related issues in `createApi.test.ts` * Fix wrong `test.each` and `describe.each` calls * Fix `console` spy related issues in `devWarnings.test.tsx` * Fix `console` spy related issues in `injectEndpoints.test.tsx` * Fix issue with test names in `queryFn.test.tsx` * Migrate docs to React 19 * Update `peerDependencies` of toolkit to include React v19 * Update React and React-DOM to the new rc version * Fix issues related to `console` spies in `createAsyncThunk.test.ts` * Fix issues related to stubbing envs in `createAsyncThunk.test.ts` * Fix issues related to `console` spies in `createReducer.test.ts` * Fix issues related to stubbing envs in `createReducer.test.ts` * Fix issues related to `console` spies in `createSlice.test.ts` * Fix issues related to stubbing envs in `createSlice.test.ts` * Fix issues related to `console` spies in `immutableStateInvariantMiddleware.test.ts` * Fix issues related to `console` spies in `serializableStateInvariantMiddleware.test.ts` * Change `.toHaveBeenCalledTimes(1)` to `.toHaveBeenCalledOnce()` * Change `.toHaveBeenCalledTimes(0)` to `.not.toHaveBeenCalled()` * Set `@types/react` and `@types/react-dom` to temporary types packages - We set the `@types/react` and `@types/react-dom` package resolutions to `npm:types-react` and `npm:types-react-dom` according to the React 19 migration guide. * Add `areErrorsEqual` equality tester - This was done to make sure `toHaveBeenCalledWith` will fail if we pass in the wrong Error constructor. For example the assertion will now fail if we pass in an `Error` instead of a `TypeError`. * Fix `console` spy related issues in `fakeBaseQuery.test.tsx` * Fix `console` spy related issues in `queryFn.test.tsx` * Remove `satisfies` operators in `queryFn.test.tsx` * Remove `jest-snapshot` from `resolutions` field - This was done because it was causing the unit tests in example workspaces to fail. * Remove `console-testing-library` as it is no longer needed * Fix minor JSX related type issues * Bump `jsdom` to version 25.0.1 * Bump `@testing-library/react` to version 16.1.0 * Properly `await` assertion in `tests/fork.test.ts` * Properly `await` assertion in `src/query/tests/errorHandling.test.tsx` * Migrate to React 19 * Bump TS version in website --------- Co-authored-by: Mark Erikson <mark@isquaredsoftware.com>
1 parent e848a55 commit 40b8aed

File tree

59 files changed

+10807
-8025
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+10807
-8025
lines changed

.github/workflows/tests.yml

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,27 @@ jobs:
6262
path: packages/toolkit/package.tgz
6363

6464
test-dist:
65-
name: Test against dist
65+
name: Run local tests against build artifact (React ${{ matrix.react.version }})
6666
needs: [build]
6767
runs-on: ubuntu-latest
6868
strategy:
6969
fail-fast: false
7070
matrix:
7171
node: ['20.x']
72+
react:
73+
[
74+
{
75+
version: '^18',
76+
types: ^18,
77+
react-dom: { version: '^18', types: '^18' },
78+
},
79+
{
80+
version: '^19',
81+
types: '^19',
82+
react-dom: { version: '^19', types: '^19' },
83+
},
84+
]
85+
7286
steps:
7387
- name: Checkout repo
7488
uses: actions/checkout@v4
@@ -82,17 +96,23 @@ jobs:
8296
- name: Install deps
8397
run: yarn install
8498

85-
- uses: actions/download-artifact@v4
99+
- name: Download build artifact
100+
uses: actions/download-artifact@v4
86101
with:
87102
name: package
88103
path: packages/toolkit
89104

90-
- run: ls -lah
105+
- name: Check folder contents
106+
run: ls -lah
107+
108+
- name: Install React ${{ matrix.react.version }} and React-DOM ${{ matrix.react.react-dom.version }}
109+
run: yarn up react@${{ matrix.react.version }} react-dom@${{ matrix.react.react-dom.version }} @types/react@${{ matrix.react.types }} @types/react-dom@${{ matrix.react.react-dom.types }}
91110

92111
- name: Install build artifact
93112
run: yarn workspace @reduxjs/toolkit add $(pwd)/package.tgz
94113

95-
- run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json
114+
- name: Erase path aliases
115+
run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json
96116

97117
- name: Run tests, against dist
98118
env:
@@ -103,7 +123,7 @@ jobs:
103123
run: rm -rf dist && yarn tsc -p . --moduleResolution Bundler --module ESNext --noEmit false --declaration --emitDeclarationOnly --outDir dist --target ESNext && rm -rf dist
104124

105125
test-types:
106-
name: 'Test Types: TS ${{ matrix.ts }}'
126+
name: 'Test Types: TS ${{ matrix.ts }} and React ${{ matrix.react.version }}'
107127

108128
needs: [build]
109129
runs-on: ubuntu-latest
@@ -112,6 +132,20 @@ jobs:
112132
matrix:
113133
node: ['20.x']
114134
ts: ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5']
135+
react:
136+
[
137+
{
138+
version: '^18',
139+
types: ^18,
140+
react-dom: { version: '^18', types: '^18' },
141+
},
142+
{
143+
version: '^19',
144+
types: '^19',
145+
react-dom: { version: '^19', types: '^19' },
146+
},
147+
]
148+
115149
steps:
116150
- name: Checkout repo
117151
uses: actions/checkout@v4
@@ -125,6 +159,9 @@ jobs:
125159
- name: Install deps
126160
run: yarn install
127161

162+
- name: Install React ${{ matrix.react.version }} and React-DOM ${{ matrix.react.react-dom.version }}
163+
run: yarn up react@${{ matrix.react.version }} react-dom@${{ matrix.react.react-dom.version }} @types/react@${{ matrix.react.types }} @types/react-dom@${{ matrix.react.react-dom.types }}
164+
128165
- name: Install TypeScript ${{ matrix.ts }}
129166
run: yarn add typescript@${{ matrix.ts }}
130167

@@ -139,7 +176,8 @@ jobs:
139176
- name: Show installed RTK versions
140177
run: yarn info @reduxjs/toolkit
141178

142-
- run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json
179+
- name: Erase path aliases
180+
run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json
143181

144182
- name: Test types
145183
env:

.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch

Lines changed: 0 additions & 68 deletions
This file was deleted.

.yarn/patches/console-testing-library__npm_0.3.1.patch

Lines changed: 0 additions & 26 deletions
This file was deleted.

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"devDependencies": {
44
"@manaflair/redux-batch": "^1.0.0",
55
"@types/nanoid": "^2.1.0",
6-
"@types/react": "^18.0",
6+
"@types/react": "^19.0.1",
77
"async-mutex": "^0.3.2",
88
"axios": "^0.20.0",
99
"formik": "^2.1.5",

docs/tutorials/quick-start.mdx

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,21 +84,30 @@ export const store = configureStore({
8484

8585
// file: index.tsx
8686
import React from 'react'
87-
import ReactDOM from 'react-dom'
87+
import { createRoot } from 'react-dom/client'
8888
import './index.css'
8989
import App from './App'
9090
// highlight-start
9191
import { store } from './app/store'
9292
import { Provider } from 'react-redux'
9393
// highlight-end
9494

95-
ReactDOM.render(
96-
// highlight-next-line
97-
<Provider store={store}>
98-
<App />
99-
</Provider>,
100-
document.getElementById('root')
101-
)
95+
const container = document.getElementById('root')
96+
97+
if (container) {
98+
const root = createRoot(container)
99+
100+
root.render(
101+
// highlight-next-line
102+
<Provider store={store}>
103+
<App />
104+
</Provider>,
105+
)
106+
} else {
107+
throw new Error(
108+
"Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
109+
)
110+
}
102111
```
103112

104113
### Create a Redux State Slice
@@ -214,18 +223,27 @@ export type RootState = ReturnType<typeof store.getState>
214223

215224
// file: index.tsx noEmit
216225
import React from 'react'
217-
import ReactDOM from 'react-dom'
226+
import { createRoot } from 'react-dom/client'
218227
import { Counter } from './features/counter/Counter'
219228
import { store } from './app/store'
220229
import { Provider } from 'react-redux'
221230

222-
ReactDOM.render(
223-
// highlight-next-line
224-
<Provider store={store}>
225-
<Counter />
226-
</Provider>,
227-
document.getElementById('root')
228-
)
231+
const container = document.getElementById('root')
232+
233+
if (container) {
234+
const root = createRoot(container)
235+
236+
root.render(
237+
// highlight-next-line
238+
<Provider store={store}>
239+
<Counter />
240+
</Provider>,
241+
)
242+
} else {
243+
throw new Error(
244+
"Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
245+
)
246+
}
229247

230248
// file: features/counter/Counter.tsx
231249
import React from 'react'

docs/tutorials/rtk-query.mdx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,19 +150,27 @@ export const store = configureStore({
150150

151151
// file: index.tsx
152152
import * as React from 'react'
153-
import { render } from 'react-dom'
153+
import { createRoot } from 'react-dom/client'
154154
import { Provider } from 'react-redux'
155155

156156
import App from './App'
157157
import { store } from './store'
158158

159-
const rootElement = document.getElementById('root')
160-
render(
161-
<Provider store={store}>
162-
<App />
163-
</Provider>,
164-
rootElement
165-
)
159+
const container = document.getElementById('root')
160+
161+
if (container) {
162+
const root = createRoot(container)
163+
164+
root.render(
165+
<Provider store={store}>
166+
<App />
167+
</Provider>,
168+
)
169+
} else {
170+
throw new Error(
171+
"Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
172+
)
173+
}
166174
```
167175

168176
## Use the query in a component

docs/usage/nextjs.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export default function StoreProvider({
181181
children: React.ReactNode
182182
}) {
183183
// highlight-start
184-
const storeRef = useRef<AppStore>()
184+
const storeRef = useRef<AppStore>(undefined)
185185
if (!storeRef.current) {
186186
// Create the store instance the first time this renders
187187
storeRef.current = makeStore()

docs/usage/usage-guide.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ configureStore({
10411041
If using Redux-Persist, you should specifically ignore all the action types it dispatches:
10421042

10431043
```jsx
1044+
import { createRoot } from 'react-dom/client'
10441045
import { configureStore } from '@reduxjs/toolkit'
10451046
import {
10461047
persistStore,
@@ -1078,14 +1079,23 @@ const store = configureStore({
10781079

10791080
let persistor = persistStore(store)
10801081

1081-
ReactDOM.render(
1082-
<Provider store={store}>
1083-
<PersistGate loading={null} persistor={persistor}>
1084-
<App />
1085-
</PersistGate>
1086-
</Provider>,
1087-
document.getElementById('root'),
1088-
)
1082+
const container = document.getElementById('root')
1083+
1084+
if (container) {
1085+
const root = createRoot(container)
1086+
1087+
root.render(
1088+
<Provider store={store}>
1089+
<PersistGate loading={null} persistor={persistor}>
1090+
<App />
1091+
</PersistGate>
1092+
</Provider>,
1093+
)
1094+
} else {
1095+
throw new Error(
1096+
"Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
1097+
)
1098+
}
10891099
```
10901100

10911101
Additionally, you can purge any persisted state by adding an extra reducer to the specific slice that you would like to clear when calling persistor.purge(). This is especially helpful when you are looking to clear persisted state on a dispatched logout action.

examples/action-listener/counter/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
"dependencies": {
66
"@reduxjs/toolkit": "^1.8.0",
77
"@types/node": "^12.0.0",
8-
"@types/react": "^18.0.12",
9-
"@types/react-dom": "^18.0.5",
8+
"@types/react": "^19.0.1",
9+
"@types/react-dom": "^19.0.1",
1010
"clsx": "1.1.1",
11-
"react": "^18.1.0",
12-
"react-dom": "^18.1.0",
13-
"react-redux": "^9.1.0",
11+
"react": "^19.0.0",
12+
"react-dom": "^19.0.0",
13+
"react-redux": "^9.1.2",
1414
"react-scripts": "5.0.1",
1515
"typescript": "~4.9"
1616
},

0 commit comments

Comments
 (0)