Skip to content

Commit 4c5546b

Browse files
committed
Merge branch 'master' of https://github.com/reduxjs/redux-toolkit into tsup-codegen
2 parents 82bd724 + 9964cdd commit 4c5546b

File tree

142 files changed

+9584
-3817
lines changed

Some content is hidden

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

142 files changed

+9584
-3817
lines changed

.github/workflows/tests.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,60 @@ jobs:
9999
TEST_DIST: true
100100
run: yarn test
101101

102+
- name: Run type tests with `moduleResolution Bundler`
103+
run: rm -rf dist && yarn tsc -p . --moduleResolution Bundler --module ESNext --noEmit false --declaration --emitDeclarationOnly --outDir dist --target ESNext && rm -rf dist
104+
105+
test-type-portability:
106+
name: Test Type Portability with TypeScript ${{ matrix.ts }} and Node.js ${{ matrix.node }}
107+
needs: [build]
108+
runs-on: ubuntu-latest
109+
strategy:
110+
fail-fast: false
111+
matrix:
112+
node: ['20.x']
113+
ts: ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', 'next']
114+
example:
115+
[
116+
{ name: 'bundler', moduleResolution: 'Bundler' },
117+
{ name: 'nodenext-cjs', moduleResolution: 'NodeNext' },
118+
{ name: 'nodenext-esm', moduleResolution: 'NodeNext' },
119+
]
120+
steps:
121+
- name: Checkout repo
122+
uses: actions/checkout@v4
123+
124+
- name: Use node ${{ matrix.node }}
125+
uses: actions/setup-node@v4
126+
with:
127+
node-version: ${{ matrix.node }}
128+
cache: 'yarn'
129+
130+
- name: Install deps
131+
run: yarn install
132+
133+
- uses: actions/download-artifact@v4
134+
with:
135+
name: package
136+
path: packages/toolkit
137+
138+
- name: Install build artifact
139+
run: yarn workspace @examples-type-portability/${{ matrix.example.name }} add $(pwd)/package.tgz
140+
141+
- name: Install TypeScript ${{ matrix.ts }}
142+
run: yarn workspace @examples-type-portability/${{ matrix.example.name }} add -D typescript@${{ matrix.ts }}
143+
144+
- name: Test type portability with `moduleResolution ${{ matrix.example.moduleResolution }}`
145+
run: yarn workspace @examples-type-portability/${{ matrix.example.name }} run test
146+
147+
- name: Test type portability with `moduleResolution Node10`
148+
run: yarn workspace @examples-type-portability/${{ matrix.example.name }} run test --module CommonJS --moduleResolution Node10 --preserveSymLinks --verbatimModuleSyntax false
149+
150+
- name: Test type portability with `moduleResolution Node10` and `type module` in `package.json`
151+
if: matrix.example.name == 'nodenext-esm' || matrix.example.name == 'bundler'
152+
run: |
153+
npm --workspace=@examples-type-portability/${{ matrix.example.name }} pkg set type=module
154+
yarn workspace @examples-type-portability/${{ matrix.example.name }} run test --module ESNext --moduleResolution Node10 --preserveSymLinks --verbatimModuleSyntax false
155+
102156
test-types:
103157
name: Test Types with TypeScript ${{ matrix.ts }}
104158

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,38 @@
1+
diff --git a/package.json b/package.json
2+
index b924e066ecfdb30917b9c1056b360834da357698..15e155bd84f9d16537ffe36f9a87debcb0ec3591 100644
3+
--- a/package.json
4+
+++ b/package.json
5+
@@ -8,12 +8,15 @@
6+
"type": "module",
7+
"main": "dist/index.js",
8+
"typings": "index.d.ts",
9+
+ "types": "index.d.ts",
10+
"exports": {
11+
".": {
12+
+ "types": "./index.d.ts",
13+
"require": "./dist/index.js",
14+
"default": "./src/index.js"
15+
},
16+
"./pure": {
17+
+ "types": "./pure.d.ts",
18+
"require": "./dist/pure.js",
19+
"default": "./src/pure.js"
20+
}
21+
diff --git a/pure.d.ts b/pure.d.ts
22+
index b13bb4eb87d0b316bb51bd6094b2353c6fc8527d..ee01cc9bd3233f5e67b050d48e22202b495a4a0a 100644
23+
--- a/pure.d.ts
24+
+++ b/pure.d.ts
25+
@@ -1 +1 @@
26+
-export * from './';
27+
+export * from './index.js';
128
diff --git a/src/index.js b/src/index.js
229
index 90ff7fa3d7d4fa62dbbf638958ae4e28abd089a8..28434687b5163b7472e86bdb11bed69e0868e660 100644
330
--- a/src/index.js
431
+++ b/src/index.js
532
@@ -1,4 +1,4 @@
633
-import { mockConsole, createConsole } from './pure';
734
+import { mockConsole, createConsole } from './pure.js';
8-
35+
936
// Keep an instance of the original console and export it
1037
const originalConsole = global.console;
1138
diff --git a/src/pure.js b/src/pure.js
@@ -15,7 +42,7 @@ index b00ea2abbaea833e336676aa46e7ced2d59d6d88..42b83ed83fa16cf2234571500fe09868
1542
@@ -228,10 +228,11 @@ export function restore() {
1643
global.console = global.originalConsole;
1744
}
18-
45+
1946
+/*
2047
if (typeof expect === 'function' && typeof expect.extend === 'function') {
2148
expect.extend({
@@ -24,13 +51,13 @@ index b00ea2abbaea833e336676aa46e7ced2d59d6d88..42b83ed83fa16cf2234571500fe09868
2451
+ // Workaround for custom inline snapshot matchers
2552
const error = new Error();
2653
const stacks = error.stack.split('\n');
27-
54+
2855
@@ -245,7 +246,6 @@ if (typeof expect === 'function' && typeof expect.extend === 'function') {
2956
error.stack = stacks.join('\n');
30-
57+
3158
const context = Object.assign(this, { error });
3259
- /* -------------------------------------------------------------- */
33-
60+
3461
const testingConsoleInstance =
3562
(received && received.testingConsole) || received;
3663
@@ -270,3 +270,4 @@ if (typeof expect === 'function' && typeof expect.extend === 'function') {

docs/api/createListenerMiddleware.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,11 +488,14 @@ To fix this, the middleware provides types for defining "pre-typed" versions of
488488
import { createListenerMiddleware, addListener } from '@reduxjs/toolkit'
489489
import type { RootState, AppDispatch } from './store'
490490

491+
declare type ExtraArgument = {foo: string};
492+
491493
export const listenerMiddleware = createListenerMiddleware()
492494

493495
export const startAppListening = listenerMiddleware.startListening.withTypes<
494496
RootState,
495-
AppDispatch
497+
AppDispatch,
498+
ExtraArgument
496499
>()
497500

498501
export const addAppListener = addListener.withTypes<RootState, AppDispatch>()

docs/rtk-query/internal/buildMiddleware/invalidationByTags.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const handler: ApiMiddlewareInternalHandler = (action, mwApi) => {
6464
1. invalidateTags function is called with a list of tags generated from the action metadata
6565
2. in the case of a [queryThunk] resolution an empty set of tags is always provided
6666
2. The tags calculated are added to the list of pending tags to invalidate (see [delayed](#Delayed))
67-
3. (optional: 'Delayed') the invalidateTags function is ended if the `apiSlice.invalidationBehaviour` is set to "delayed" and there are any pending thunks/queries running in that `apiSlice`
67+
3. (optional: 'Delayed') the invalidateTags function is ended if the `apiSlice.invalidationBehavior` is set to "delayed" and there are any pending thunks/queries running in that `apiSlice`
6868
4. Pending tags are reset to an empty list, if there are no tags the function ends here
6969
5. Selects all `{ endpointName, originalArgs, queryCacheKey }` combinations that would be invalidated by a specific set of tags.
7070
6. Iterates through queryCacheKeys selected and performs one of two actions if the query exists\*
@@ -102,7 +102,7 @@ Step 6 is performed within a `context.batch()` call.
102102
103103
RTKQ now has internal logic to delay tag invalidation briefly, to allow multiple invalidations to get handled together. This is controlled by a new `invalidationBehavior: 'immediate' | 'delayed'` flag on `createApi`. The new default behavior is `'delayed'`. Set it to `'immediate'` to revert to the behavior in RTK 1.9.
104104
105-
The `'delayed'` behaviour enables a check inside `invalidationByTags` that will cause any invalidation that is triggered while a query/mutation is still pending to batch the invalidation until no query/mutation is running.
105+
The `'delayed'` behavior enables a check inside `invalidationByTags` that will cause any invalidation that is triggered while a query/mutation is still pending to batch the invalidation until no query/mutation is running.
106106
107107
```ts no-transpile
108108
function invalidateTags(

examples/publish-ci/react-native/App.test.tsx

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
import type { Action, Dispatch } from "@reduxjs/toolkit"
2+
import { configureStore } from "@reduxjs/toolkit"
13
import { screen, waitFor } from "@testing-library/react-native"
4+
import { Component, PureComponent, type PropsWithChildren } from "react"
5+
import type { TextStyle } from "react-native"
6+
import { Button, Text, View } from "react-native"
7+
import { connect, Provider } from "react-redux"
28
import { App } from "./App"
39
import { renderWithProviders } from "./src/utils/test-utils"
410

@@ -103,3 +109,207 @@ test("Add If Odd should work as expected", async () => {
103109
await user.press(screen.getByText("Add If Odd"))
104110
expect(screen.getByLabelText("Count")).toHaveTextContent("4")
105111
})
112+
113+
test("React-Redux issue #2150: Nested component updates should be properly batched when using connect", async () => {
114+
// Original Issue: https://github.com/reduxjs/react-redux/issues/2150
115+
// Solution: https://github.com/reduxjs/react-redux/pull/2156
116+
117+
// Actions
118+
const ADD = "ADD"
119+
const DATE = "DATE"
120+
121+
// Action types
122+
interface AddAction extends Action<string> {}
123+
interface DateAction extends Action<string> {
124+
payload?: { date: number }
125+
}
126+
127+
// Reducer states
128+
interface DateState {
129+
date: number | null
130+
}
131+
132+
interface CounterState {
133+
count: number
134+
}
135+
136+
// Reducers
137+
const dateReducer = (
138+
state: DateState = { date: null },
139+
action: DateAction,
140+
) => {
141+
switch (action.type) {
142+
case DATE:
143+
return {
144+
...state,
145+
date: action.payload?.date ?? null,
146+
}
147+
default:
148+
return state
149+
}
150+
}
151+
152+
const counterReducer = (
153+
state: CounterState = { count: 0 },
154+
action: AddAction,
155+
) => {
156+
switch (action.type) {
157+
case ADD:
158+
return {
159+
...state,
160+
count: state.count + 1,
161+
}
162+
default:
163+
return state
164+
}
165+
}
166+
167+
// Store
168+
const store = configureStore({
169+
reducer: {
170+
counter: counterReducer,
171+
dates: dateReducer,
172+
},
173+
})
174+
175+
// ======== COMPONENTS =========
176+
interface CounterProps {
177+
count?: number
178+
date?: number | null
179+
dispatch: Dispatch<AddAction | DateAction>
180+
testID?: string
181+
}
182+
183+
class CounterRaw extends PureComponent<CounterProps> {
184+
handleIncrement = () => {
185+
this.props.dispatch({ type: ADD })
186+
}
187+
188+
handleDate = () => {
189+
this.props.dispatch({ type: DATE, payload: { date: Date.now() } })
190+
}
191+
192+
render() {
193+
return (
194+
<View style={{ paddingVertical: 20 }}>
195+
<Text testID={`${this.props.testID}-child`}>
196+
Counter Value: {this.props.count}
197+
</Text>
198+
<Text>date Value: {this.props.date}</Text>
199+
</View>
200+
)
201+
}
202+
}
203+
204+
class ButtonsRaw extends PureComponent<CounterProps> {
205+
handleIncrement = () => {
206+
this.props.dispatch({ type: ADD })
207+
}
208+
209+
handleDate = () => {
210+
this.props.dispatch({ type: DATE, payload: { date: Date.now() } })
211+
}
212+
213+
render() {
214+
return (
215+
<View>
216+
<Button title="Update Date" onPress={this.handleDate} />
217+
<View style={{ height: 20 }} />
218+
<Button title="Increment Counter" onPress={this.handleIncrement} />
219+
</View>
220+
)
221+
}
222+
}
223+
224+
const mapStateToProps = (state: {
225+
counter: CounterState
226+
dates: DateState
227+
}) => {
228+
return { count: state.counter.count, date: state.dates.date }
229+
}
230+
231+
const mapDispatchToProps = (dispatch: Dispatch<AddAction | DateAction>) => ({
232+
dispatch,
233+
})
234+
235+
const Buttons = connect(null, mapDispatchToProps)(ButtonsRaw)
236+
const Counter = connect(mapStateToProps, mapDispatchToProps)(CounterRaw)
237+
238+
class Container extends PureComponent<PropsWithChildren> {
239+
render() {
240+
return this.props.children
241+
}
242+
}
243+
244+
const mapStateToPropsBreaking = (_state: any) => ({})
245+
246+
const ContainerBad = connect(mapStateToPropsBreaking, null)(Container)
247+
248+
const mapStateToPropsNonBlocking1 = (state: { counter: CounterState }) => ({
249+
count: state.counter.count,
250+
})
251+
252+
const ContainerNonBlocking1 = connect(
253+
mapStateToPropsNonBlocking1,
254+
null,
255+
)(Container)
256+
257+
const mapStateToPropsNonBlocking2 = (state: any) => ({ state })
258+
259+
const ContainerNonBlocking2 = connect(
260+
mapStateToPropsNonBlocking2,
261+
null,
262+
)(Container)
263+
264+
class MainApp extends Component {
265+
render() {
266+
const $H1: TextStyle = { fontSize: 20 }
267+
return (
268+
<Provider store={store}>
269+
<Buttons />
270+
<Text style={$H1}>=Expected=</Text>
271+
<View>
272+
<Text>
273+
I don't have a parent blocking state updates so I should behave as
274+
expected
275+
</Text>
276+
<Counter />
277+
</View>
278+
279+
<Text style={$H1}>=Undesired behavior with react-redux 9.x=</Text>
280+
<ContainerBad>
281+
<Text>All redux state updates blocked</Text>
282+
<Counter testID="undesired" />
283+
</ContainerBad>
284+
285+
<Text style={$H1}>=Partially working in 9.x=</Text>
286+
<ContainerNonBlocking1>
287+
<Text>
288+
I'm inconsistent, if date updates first I don't see it, but once
289+
count updates I rerender with count or date changes
290+
</Text>
291+
<Counter testID="inconsistent" />
292+
</ContainerNonBlocking1>
293+
294+
<Text style={$H1}>=Poor workaround for 9.x?=</Text>
295+
<ContainerNonBlocking2>
296+
<Text>I see all state changes</Text>
297+
<Counter />
298+
</ContainerNonBlocking2>
299+
</Provider>
300+
)
301+
}
302+
}
303+
304+
const { user, getByTestId, getByText } = renderWithProviders(<MainApp />)
305+
306+
expect(getByTestId("undesired-child")).toHaveTextContent("Counter Value: 0")
307+
308+
await user.press(getByText("Increment Counter"))
309+
310+
expect(getByTestId("inconsistent-child")).toHaveTextContent(
311+
"Counter Value: 1",
312+
)
313+
314+
expect(getByTestId("undesired-child")).toHaveTextContent("Counter Value: 1")
315+
})

examples/publish-ci/react-native/babel.config.cts

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

0 commit comments

Comments
 (0)