From c5b983c591e2524eb63bf1f4316c0216b2d491ef Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 31 Jul 2024 13:05:55 -0500 Subject: [PATCH 1/8] Convert `babel.config.cts` to `babel.config.js` --- .../publish-ci/react-native/{babel.config.cts => babel.config.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/publish-ci/react-native/{babel.config.cts => babel.config.js} (100%) diff --git a/examples/publish-ci/react-native/babel.config.cts b/examples/publish-ci/react-native/babel.config.js similarity index 100% rename from examples/publish-ci/react-native/babel.config.cts rename to examples/publish-ci/react-native/babel.config.js From 627bec0c80bdc592ede19e34347ef071987d0b76 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 31 Jul 2024 13:07:41 -0500 Subject: [PATCH 2/8] Fix `babel.config.js` file --- examples/publish-ci/react-native/babel.config.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/publish-ci/react-native/babel.config.js b/examples/publish-ci/react-native/babel.config.js index f47f36ed05..08293c3ab8 100644 --- a/examples/publish-ci/react-native/babel.config.js +++ b/examples/publish-ci/react-native/babel.config.js @@ -1,7 +1,14 @@ -import type { TransformOptions } from "@babel/core" +/** @import { ConfigFunction } from "@babel/core" */ -const config: TransformOptions = { - presets: ["module:@react-native/babel-preset"], +/** + * @satisfies {ConfigFunction} + */ +const config = api => { + api.cache.using(() => process.env.NODE_ENV) + + return { + presets: [["module:@react-native/babel-preset"]], + } } -export default config +module.exports = config From 15e2ba71fc6716cab9b03651d32e78bac630324d Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 31 Jul 2024 13:08:49 -0500 Subject: [PATCH 3/8] Fix `jest.config.ts` --- .../publish-ci/react-native/jest.config.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/publish-ci/react-native/jest.config.ts b/examples/publish-ci/react-native/jest.config.ts index efdcf5d53e..5d644f5a7f 100644 --- a/examples/publish-ci/react-native/jest.config.ts +++ b/examples/publish-ci/react-native/jest.config.ts @@ -2,7 +2,26 @@ import type { Config } from "jest" const config: Config = { preset: "react-native", - testEnvironment: "node", + verbose: true, + /** + * Without this we will get the following error: + * `SyntaxError: Cannot use import statement outside a module` + */ + transformIgnorePatterns: [ + "node_modules/(?!((jest-)?react-native|...|react-redux))", + ], + /** + * React Native's `jest` preset includes a + * [polyfill for `window`](https://github.com/facebook/react-native/blob/acb634bc9662c1103bc7c8ca83cfdc62516d0060/packages/react-native/jest/setup.js#L61-L66). + * This polyfill causes React-Redux to use `useEffect` + * instead of `useLayoutEffect` for the `useIsomorphicLayoutEffect` hook. + * As a result, nested component updates may not be properly batched + * when using the `connect` API, leading to potential issues. + */ + globals: { + window: undefined, + navigator: { product: "ReactNative" }, + }, setupFilesAfterEnv: ["/jest-setup.ts"], fakeTimers: { enableGlobally: true }, } From 03b9f31c57f770352494d1b4f63d2c1e57482ce3 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 31 Jul 2024 13:11:42 -0500 Subject: [PATCH 4/8] Add new React-Redux related unit test - Added a unit test for [React-redux #2150](https://github.com/reduxjs/react-redux/issues/2150) --- examples/publish-ci/react-native/App.test.tsx | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/examples/publish-ci/react-native/App.test.tsx b/examples/publish-ci/react-native/App.test.tsx index 6967d32bb8..31dc73deae 100644 --- a/examples/publish-ci/react-native/App.test.tsx +++ b/examples/publish-ci/react-native/App.test.tsx @@ -1,4 +1,10 @@ +import type { Action, Dispatch } from "@reduxjs/toolkit" +import { configureStore } from "@reduxjs/toolkit" import { screen, waitFor } from "@testing-library/react-native" +import { Component, PureComponent, type PropsWithChildren } from "react" +import type { TextStyle } from "react-native" +import { Button, Text, View } from "react-native" +import { connect, Provider } from "react-redux" import { App } from "./App" import { renderWithProviders } from "./src/utils/test-utils" @@ -103,3 +109,207 @@ test("Add If Odd should work as expected", async () => { await user.press(screen.getByText("Add If Odd")) expect(screen.getByLabelText("Count")).toHaveTextContent("4") }) + +test("React-Redux issue #2150: Nested component updates should be properly batched when using connect", async () => { + // Original Issue: https://github.com/reduxjs/react-redux/issues/2150 + // Solution: https://github.com/reduxjs/react-redux/pull/2156 + + // Actions + const ADD = "ADD" + const DATE = "DATE" + + // Action types + interface AddAction extends Action {} + interface DateAction extends Action { + payload?: { date: number } + } + + // Reducer states + interface DateState { + date: number | null + } + + interface CounterState { + count: number + } + + // Reducers + const dateReducer = ( + state: DateState = { date: null }, + action: DateAction, + ) => { + switch (action.type) { + case DATE: + return { + ...state, + date: action.payload?.date ?? null, + } + default: + return state + } + } + + const counterReducer = ( + state: CounterState = { count: 0 }, + action: AddAction, + ) => { + switch (action.type) { + case ADD: + return { + ...state, + count: state.count + 1, + } + default: + return state + } + } + + // Store + const store = configureStore({ + reducer: { + counter: counterReducer, + dates: dateReducer, + }, + }) + + // ======== COMPONENTS ========= + interface CounterProps { + count?: number + date?: number | null + dispatch: Dispatch + testID?: string + } + + class CounterRaw extends PureComponent { + handleIncrement = () => { + this.props.dispatch({ type: ADD }) + } + + handleDate = () => { + this.props.dispatch({ type: DATE, payload: { date: Date.now() } }) + } + + render() { + return ( + + + Counter Value: {this.props.count} + + date Value: {this.props.date} + + ) + } + } + + class ButtonsRaw extends PureComponent { + handleIncrement = () => { + this.props.dispatch({ type: ADD }) + } + + handleDate = () => { + this.props.dispatch({ type: DATE, payload: { date: Date.now() } }) + } + + render() { + return ( + +