Skip to content

Commit 538c857

Browse files
React Native bundle (#114)
Summary: Fixes facebookexperimental/Recoil#99 **Additions:** - Add the ability to set/get a batch function, which defaults to `ReactBatchedUpdates`. - Add `ReactBatchedUpdates` files for Web and Native that are imported to set the batch function to the renderer-specific function. - Add a `native` target to Rollup, which resolves `ReactBatchedUpdates`. **Result:** This gives us out of the box support for React DOM and React Native, with the library user not needing to make any changes. **Notes:** I haven't changed the use of `unstable_batchedUpdates` in the tests. (In fact I haven't been able to get the suite to pass on `master`, which I'll have another go at later, and file an issue if I'm still stuck.) But it'd be great if the maintainers or anyone with more contexts on the tests could weigh in here. It seems like we should use `unstable_batchedUpdates` from `react-test-renderer` here. In future, we could add a separate entry point (e.g. `'recoil/alternate-renderers'`) that doesn't set a batch function and doesn't depend on `react-dom` or `react-native`. We should be able to do this really easily because the infrastructure for setting the batching function to anything is available (and already exported in this PR). **Prior art for this PR:** - React Redux: [Alternate renderers entry point](https://github.com/reduxjs/react-redux/blob/master/src/alternate-renderers.js) and [get/set batch functions](https://github.com/reduxjs/react-redux/blob/master/src/utils/batch.js) - dustinsoftware's [PR](facebookexperimental/Recoil#108) with a similar implementation {emoji:1f64c} Pull Request resolved: facebookexperimental/Recoil#114 Reviewed By: drarmstr, davidmccabe Differential Revision: D23733076 Pulled By: mondaychen fbshipit-source-id: 948d45dd43b6e9ebac86ed6dd8433f3a1207c799
1 parent 9c5a972 commit 538c857

13 files changed

+201
-23
lines changed

.babelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"alias": {
99
"React": "react",
1010
"ReactDOM": "react-dom",
11+
"ReactNative": "react-native",
1112
"ReactTestUtils": "react-dom/test-utils"
1213
}
1314
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ typings/
8686
.nuxt
8787
cjs
8888
es
89+
native
8990
umd
9091
/index.d.ts
9192

package.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
"description": "Recoil - A state management library for React",
55
"main": "cjs/recoil.js",
66
"module": "es/recoil.js",
7+
"react-native": "native/recoil.js",
78
"unpkg": "umd/recoil.js",
89
"files": [
910
"umd",
1011
"es",
1112
"cjs",
13+
"native",
1214
"index.d.ts"
1315
],
1416
"repository": "https://github.com/facebookexperimental/recoil.git",
@@ -24,8 +26,15 @@
2426
"lint": "eslint ."
2527
},
2628
"peerDependencies": {
27-
"react": "^16.13.1",
28-
"react-dom": "^16.13.1"
29+
"react": "^16.13.1"
30+
},
31+
"peerDependenciesMeta": {
32+
"react-dom": {
33+
"optional": true
34+
},
35+
"react-native": {
36+
"optional": true
37+
}
2938
},
3039
"devDependencies": {
3140
"@babel/core": "^7.9.6",

rollup.config.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {terser} from 'rollup-plugin-terser';
77
const inputFile = 'src/Recoil_index.js';
88
const externalLibs = ['react', 'react-dom'];
99

10+
const defaultNodeResolveConfig = {};
11+
const nodeResolvePlugin = nodeResolve(defaultNodeResolveConfig);
12+
1013
const commonPlugins = [
1114
babel({
1215
presets: ['@babel/preset-react', '@babel/preset-flow'],
@@ -27,10 +30,13 @@ const commonPlugins = [
2730
if (source === 'ReactDOM') {
2831
return {id: 'react-dom', external: true};
2932
}
33+
if (source === 'ReactNative') {
34+
return {id: 'react-native', external: true};
35+
}
3036
return null;
3137
},
3238
},
33-
nodeResolve(),
39+
nodeResolvePlugin,
3440
commonjs(),
3541
];
3642

@@ -74,6 +80,28 @@ const configs = [
7480
plugins: commonPlugins,
7581
},
7682

83+
// React Native
84+
{
85+
input: inputFile,
86+
output: {
87+
file: `native/recoil.js`,
88+
format: 'es',
89+
exports: 'named',
90+
},
91+
external: [...externalLibs, 'react-native'],
92+
plugins: commonPlugins.map(plugin => {
93+
// Replace the default nodeResolve plugin
94+
if (plugin === nodeResolvePlugin) {
95+
return nodeResolve({
96+
...defaultNodeResolveConfig,
97+
extensions: ['.native.js', '.js'],
98+
});
99+
}
100+
101+
return plugin;
102+
}),
103+
},
104+
77105
// ES for Browsers
78106
{
79107
input: inputFile,

src/Recoil_index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const {
6969
waitForAny,
7070
waitForNone,
7171
} = require('./recoil_values/Recoil_WaitFor');
72+
const {batchUpdates, setBatcher} = require('./util/Recoil_batcher');
7273

7374
module.exports = {
7475
// Types
@@ -115,4 +116,8 @@ module.exports = {
115116

116117
// Other functions
117118
isRecoilValue,
119+
120+
// Batching
121+
batchUpdates,
122+
setBatcher,
118123
};

src/core/Recoil_Node.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ function registerNode<T>(node: Node<T>): RecoilValue<T> {
9797
// @fb-only: if (typeof isAcceptingUpdate !== 'function' || !isAcceptingUpdate()) {
9898
// @fb-only: expectationViolation(message, 'recoil');
9999
// @fb-only: }
100-
// prettier-ignore
100+
// prettier-ignore
101101
// @fb-only: } else {
102-
recoverableViolation(message, 'recoil');
102+
// @fb-only: recoverableViolation(message, 'recoil');
103103
// @fb-only: }
104+
console.warn(message); // @oss-only
104105
}
105106
nodes.set(node.key, node);
106107

src/hooks/Recoil_Hooks.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import type {NodeKey, Store, TreeState} from '../core/Recoil_State';
1919

2020
const React = require('React');
2121
const {useCallback, useEffect, useMemo, useRef, useState} = require('React');
22-
const ReactDOM = require('ReactDOM');
2322

2423
const {DEFAULT_VALUE, getNode, nodes} = require('../core/Recoil_Node');
2524
const {
@@ -36,6 +35,7 @@ const {
3635
subscribeToRecoilValue,
3736
} = require('../core/Recoil_RecoilValueInterface');
3837
const {Snapshot, cloneSnapshot} = require('../core/Recoil_Snapshot');
38+
const {batchUpdates} = require('../util/Recoil_batcher');
3939
const {setByAddingToSet} = require('../util/Recoil_CopyOnWrite');
4040
const differenceSets = require('../util/Recoil_differenceSets');
4141
const expectationViolation = require('../util/Recoil_expectationViolation');
@@ -599,7 +599,7 @@ function useGotoRecoilSnapshot(): Snapshot => void {
599599
const storeState = storeRef.current.getState();
600600
const prev = storeState.nextTree ?? storeState.currentTree;
601601
const next = snapshot.getStore_INTERNAL().getState().currentTree;
602-
ReactDOM.unstable_batchedUpdates(() => {
602+
batchUpdates(() => {
603603
const keysToUpdate = new Set();
604604
for (const keys of [prev.atomValues.keys(), next.atomValues.keys()]) {
605605
for (const key of keys) {
@@ -639,7 +639,7 @@ function useSetUnvalidatedAtomValues(): (
639639
) => void {
640640
const storeRef = useStoreRef();
641641
return (values: Map<NodeKey, mixed>, transactionMetadata: {...} = {}) => {
642-
ReactDOM.unstable_batchedUpdates(() => {
642+
batchUpdates(() => {
643643
storeRef.current.addTransactionMetadata(transactionMetadata);
644644
values.forEach((value, key) =>
645645
setUnvalidatedRecoilValue(
@@ -686,13 +686,13 @@ function useRecoilCallback<Args: $ReadOnlyArray<mixed>, Return>(
686686
}
687687

688688
let ret = SENTINEL;
689-
ReactDOM.unstable_batchedUpdates(() => {
689+
batchUpdates(() => {
690690
// flowlint-next-line unclear-type:off
691691
ret = (fn: any)({set, reset, snapshot, gotoSnapshot})(...args);
692692
});
693693
invariant(
694694
!(ret instanceof Sentinel),
695-
'unstable_batchedUpdates should return immediately',
695+
'batchUpdates should return immediately',
696696
);
697697
return (ret: Return);
698698
},

src/hooks/__tests__/Recoil_PublicHooks-test.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,17 @@ const {
3535
renderElements,
3636
renderElementsWithSuspenseCount,
3737
} = require('../../testing/Recoil_TestingUtils');
38+
const {batchUpdates} = require('../../util/Recoil_batcher');
3839
const gkx = require('../../util/Recoil_gkx');
3940
const {
41+
recoilComponentGetRecoilValueCount_FOR_TESTING,
4042
useRecoilState,
4143
useRecoilStateLoadable,
4244
useRecoilValue,
4345
useRecoilValueLoadable,
4446
useSetRecoilState,
4547
useSetUnvalidatedAtomValues,
4648
useTransactionObservation_DEPRECATED,
47-
recoilComponentGetRecoilValueCount_FOR_TESTING,
4849
} = require('../Recoil_Hooks');
4950

5051
gkx.setFail('recoil_async_selector_refactor');
@@ -480,7 +481,7 @@ test('Selector functions are evaluated just once even if multiple upstreams chan
480481
);
481482
expect(selectorFn).toHaveBeenCalledTimes(1);
482483
act(() => {
483-
ReactDOM.unstable_batchedUpdates(() => {
484+
batchUpdates(() => {
484485
updateValueA(1);
485486
updateValueB(1);
486487
});
@@ -504,7 +505,7 @@ test('Component that depends on multiple atoms via selector is rendered just onc
504505
);
505506
expect(commit).toHaveBeenCalledTimes(1);
506507
act(() => {
507-
ReactDOM.unstable_batchedUpdates(() => {
508+
batchUpdates(() => {
508509
updateValueA(1);
509510
updateValueB(1);
510511
});
@@ -527,7 +528,7 @@ test('Component that depends on multiple atoms directly is rendered just once',
527528
);
528529
expect(ReadComp).toHaveBeenCalledTimes(1);
529530
act(() => {
530-
ReactDOM.unstable_batchedUpdates(() => {
531+
batchUpdates(() => {
531532
updateValueA(1);
532533
updateValueB(1);
533534
});
@@ -547,7 +548,7 @@ test('Component is rendered just once when atom is changed twice', () => {
547548
);
548549
expect(commit).toHaveBeenCalledTimes(1);
549550
act(() => {
550-
ReactDOM.unstable_batchedUpdates(() => {
551+
batchUpdates(() => {
551552
updateValueA(1);
552553
updateValueA(2);
553554
});
@@ -628,7 +629,7 @@ test('Can subscribe to and also change an atom in the same batch', () => {
628629
expect(container.textContent).toEqual('');
629630

630631
act(() => {
631-
ReactDOM.unstable_batchedUpdates(() => {
632+
batchUpdates(() => {
632633
setVisible(true);
633634
updateValue(1337);
634635
});
@@ -944,7 +945,7 @@ test('Selector depedencies are updated transactionally', () => {
944945
);
945946

946947
act(() => {
947-
ReactDOM.unstable_batchedUpdates(() => {
948+
batchUpdates(() => {
948949
updateValueA(1);
949950
updateValueB(1);
950951
});

src/recoil_values/Recoil_atomFamily.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ function atomFamily<T, P: Parameter>(
9393
// @fb-only: if (
9494
// @fb-only: options.scopeRules_APPEND_ONLY_READ_THE_DOCS
9595
// @fb-only: ) {
96-
// @fb-only: legacyAtom = parameterizedScopedAtomLegacy<T | DefaultValue, P>({
97-
// @fb-only: ...legacyAtomOptions,
98-
// @fb-only: scopeRules_APPEND_ONLY_READ_THE_DOCS:
99-
// @fb-only: options.scopeRules_APPEND_ONLY_READ_THE_DOCS,
100-
// @fb-only: });
96+
// @fb-only: legacyAtom = parameterizedScopedAtomLegacy<T | DefaultValue, P>({
97+
// @fb-only: ...legacyAtomOptions,
98+
// @fb-only: scopeRules_APPEND_ONLY_READ_THE_DOCS:
99+
// @fb-only: options.scopeRules_APPEND_ONLY_READ_THE_DOCS,
100+
// @fb-only: });
101101
// @fb-only: } else {
102-
legacyAtom = atom<T | DefaultValue>(legacyAtomOptions);
102+
legacyAtom = atom<T | DefaultValue>(legacyAtomOptions);
103103
// @fb-only: }
104104

105105
// Selector to calculate the default value based on any persisted legacy atoms
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails oncall+recoil
8+
* @flow strict
9+
* @format
10+
*
11+
* This is to export esstiential functions from react-dom
12+
* for our web build
13+
*/
14+
15+
const {unstable_batchedUpdates} = require('ReactDOM');
16+
module.exports = {unstable_batchedUpdates};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails oncall+recoil
8+
* @flow strict
9+
* @format
10+
*
11+
* This is to export esstiential functions from react-native
12+
* for our react-native build (currently only available on github)
13+
*/
14+
15+
// $FlowExpectedError[cannot-resolve-module]
16+
const {unstable_batchedUpdates} = require('ReactNative'); // @oss-only
17+
module.exports = {unstable_batchedUpdates}; // @oss-only

src/util/Recoil_batcher.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails oncall+recoil
8+
* @flow strict
9+
* @format
10+
*
11+
* This is a stub for some integration into FB internal stuff
12+
*/
13+
14+
const {unstable_batchedUpdates} = require('./Recoil_ReactBatchedUpdates');
15+
16+
let batcher = unstable_batchedUpdates;
17+
18+
// flowlint-next-line unclear-type:off
19+
type Callback = () => any;
20+
type Batcher = (callback: Callback) => void;
21+
22+
/**
23+
* Sets the provided batcher function as the batcher function used by Recoil.
24+
*
25+
* Set the batcher to a custom batcher for your renderer,
26+
* if you use a renderer other than React DOM or React Native.
27+
*/
28+
const setBatcher: Batcher => void = (newBatcher: Batcher) => {
29+
batcher = newBatcher;
30+
};
31+
32+
/**
33+
* Returns the current batcher function.
34+
*/
35+
const getBatcher: () => Batcher = () => batcher;
36+
37+
/**
38+
* Calls the current batcher function and passes the
39+
* provided callback function.
40+
*/
41+
const batchUpdates: Callback => void = (callback: Callback) =>
42+
batcher(callback);
43+
44+
module.exports = {
45+
getBatcher,
46+
setBatcher,
47+
batchUpdates,
48+
};

0 commit comments

Comments
 (0)