Skip to content

Commit b832f83

Browse files
authored
Fix RN batching and effect behavior (#1444)
* Add React Native deps for testing * Add a Jest config to run tests in an RN environment * Add initial RN tests * Add test for RN batch export * Add jest-native testing assertions * Extract useIsomorphicLayoutEffect utility * Add additional RN batching-related tests * 7.1.2-alpha.0
1 parent b5034a6 commit b832f83

8 files changed

+3699
-100
lines changed

jest.config.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const defaults = {
2+
coverageDirectory: './coverage/',
3+
collectCoverage: true,
4+
testURL: 'http://localhost'
5+
}
6+
7+
const testFolderPath = folderName => `<rootDir>/test/${folderName}/**/*.js`
8+
9+
const NORMAL_TEST_FOLDERS = ['components', 'hooks', 'integration', 'utils']
10+
11+
const standardConfig = {
12+
...defaults,
13+
displayName: 'ReactDOM',
14+
testMatch: NORMAL_TEST_FOLDERS.map(testFolderPath)
15+
}
16+
17+
const rnConfig = {
18+
...defaults,
19+
displayName: 'React Native',
20+
testMatch: [testFolderPath('react-native')],
21+
preset: 'react-native',
22+
transform: {
23+
'^.+\\.js$': '<rootDir>/node_modules/react-native/jest/preprocessor.js'
24+
}
25+
}
26+
27+
module.exports = {
28+
projects: [standardConfig, rnConfig]
29+
}

package-lock.json

+3,139-51
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-redux",
3-
"version": "7.1.1",
3+
"version": "7.1.2-alpha.0",
44
"description": "Official React bindings for Redux",
55
"keywords": [
66
"react",
@@ -65,8 +65,10 @@
6565
"@babel/plugin-transform-runtime": "^7.5.5",
6666
"@babel/preset-env": "^7.5.5",
6767
"@testing-library/jest-dom": "^4.1.0",
68+
"@testing-library/jest-native": "^3.0.2",
6869
"@testing-library/react": "^8.0.8",
6970
"@testing-library/react-hooks": "^1.1.0",
71+
"@testing-library/react-native": "^4.2.0",
7072
"babel-eslint": "^10.0.3",
7173
"babel-jest": "^24.9.0",
7274
"codecov": "^3.5.0",
@@ -83,6 +85,7 @@
8385
"prettier": "^1.18.2",
8486
"react": "^16.8.6",
8587
"react-dom": "^16.8.6",
88+
"react-native": "^0.61.4",
8689
"react-test-renderer": "^16.8.6",
8790
"redux": "^4.0.4",
8891
"rimraf": "^3.0.0",
@@ -97,10 +100,5 @@
97100
"transform": [
98101
"loose-envify"
99102
]
100-
},
101-
"jest": {
102-
"coverageDirectory": "./coverage/",
103-
"collectCoverage": true,
104-
"testURL": "http://localhost"
105103
}
106104
}

src/components/connectAdvanced.js

+2-20
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import hoistStatics from 'hoist-non-react-statics'
22
import invariant from 'invariant'
3-
import React, {
4-
useContext,
5-
useMemo,
6-
useEffect,
7-
useLayoutEffect,
8-
useRef,
9-
useReducer
10-
} from 'react'
3+
import React, { useContext, useMemo, useRef, useReducer } from 'react'
114
import { isValidElementType, isContextConsumer } from 'react-is'
125
import Subscription from '../utils/Subscription'
6+
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
137

148
import { ReactReduxContext } from './Context'
159

@@ -32,18 +26,6 @@ function storeStateUpdatesReducer(state, action) {
3226

3327
const initStateUpdates = () => [null, 0]
3428

35-
// React currently throws a warning when using useLayoutEffect on the server.
36-
// To get around it, we can conditionally useEffect on the server (no-op) and
37-
// useLayoutEffect in the browser. We need useLayoutEffect because we want
38-
// `connect` to perform sync updates to a ref to save the latest props after
39-
// a render is actually committed to the DOM.
40-
const useIsomorphicLayoutEffect =
41-
typeof window !== 'undefined' &&
42-
typeof window.document !== 'undefined' &&
43-
typeof window.document.createElement !== 'undefined'
44-
? useLayoutEffect
45-
: useEffect
46-
4729
export default function connectAdvanced(
4830
/*
4931
selectorFactory is a func that is responsible for returning the selector function used to

src/hooks/useSelector.js

+2-23
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,10 @@
1-
import {
2-
useReducer,
3-
useRef,
4-
useEffect,
5-
useMemo,
6-
useLayoutEffect,
7-
useContext
8-
} from 'react'
1+
import { useReducer, useRef, useMemo, useContext } from 'react'
92
import invariant from 'invariant'
103
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
114
import Subscription from '../utils/Subscription'
5+
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
126
import { ReactReduxContext } from '../components/Context'
137

14-
// React currently throws a warning when using useLayoutEffect on the server.
15-
// To get around it, we can conditionally useEffect on the server (no-op) and
16-
// useLayoutEffect in the browser. We need useLayoutEffect to ensure the store
17-
// subscription callback always has the selector from the latest render commit
18-
// available, otherwise a store update may happen between render and the effect,
19-
// which may cause missed updates; we also must ensure the store subscription
20-
// is created synchronously, otherwise a store update may occur before the
21-
// subscription is created and an inconsistent state may be observed
22-
const useIsomorphicLayoutEffect =
23-
typeof window !== 'undefined' &&
24-
typeof window.document !== 'undefined' &&
25-
typeof window.document.createElement !== 'undefined'
26-
? useLayoutEffect
27-
: useEffect
28-
298
const refEquality = (a, b) => a === b
309

3110
function useSelectorWithStoreAndSubscription(
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useEffect, useLayoutEffect } from 'react'
2+
3+
// React currently throws a warning when using useLayoutEffect on the server.
4+
// To get around it, we can conditionally useEffect on the server (no-op) and
5+
// useLayoutEffect in the browser. We need useLayoutEffect to ensure the store
6+
// subscription callback always has the selector from the latest render commit
7+
// available, otherwise a store update may happen between render and the effect,
8+
// which may cause missed updates; we also must ensure the store subscription
9+
// is created synchronously, otherwise a store update may occur before the
10+
// subscription is created and an inconsistent state may be observed
11+
12+
const isHopefullyDomEnvironment =
13+
typeof window !== 'undefined' &&
14+
typeof window.document !== 'undefined' &&
15+
typeof window.document.createElement !== 'undefined'
16+
17+
export const useIsomorphicLayoutEffect = isHopefullyDomEnvironment
18+
? useLayoutEffect
19+
: useEffect
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { useLayoutEffect } from 'react'
2+
3+
// Under React Native, we know that we always want to use useLayoutEffect
4+
5+
export const useIsomorphicLayoutEffect = useLayoutEffect

0 commit comments

Comments
 (0)