Delete create-subscription folder (#24288)
dan committed
Apr 11, 2022 at 20:07 UTC
d9a0f9e20338f05f0992562075ff034ccde008fe
7 files changed
-891
packages/create-subscription/README.md
deleted
-169
@@ -1,169 +0,0 @@
1
-# create-subscription
2
-
3
-`create-subscription` is a utility for subscribing to external data sources inside React components.
4
-
5
-**It is no longer maintained and will not be updated. Use the built-in [`React.useSyncExternalStore`](https://reactjs.org/docs/hooks-reference.html#usesyncexternalstore) instead.**
6
-
7
-# Installation
8
-
9
-```sh
10
-# Yarn
11
-yarn add create-subscription
12
-
13
-# NPM
14
-npm install create-subscription
15
-```
16
-
17
-# Usage
18
-
19
-To configure a subscription, you must provide two methods: `getCurrentValue` and `subscribe`.
20
-
21
-```js
22
-import { createSubscription } from "create-subscription";
23
-
24
-const Subscription = createSubscription({
25
- getCurrentValue(source) {
26
- // Return the current value of the subscription (source),
27
- // or `undefined` if the value can't be read synchronously (e.g. native Promises).
28
- },
29
- subscribe(source, callback) {
30
- // Subscribe (e.g. add an event listener) to the subscription (source).
31
- // Call callback(newValue) whenever a subscription changes.
32
- // Return an unsubscribe method,
33
- // Or a no-op if unsubscribe is not supported (e.g. native Promises).
34
- }
35
-});
36
-```
37
-
38
-To use the `Subscription` component, pass the subscribable property (e.g. an event dispatcher, observable) as the `source` property and use a [render prop](https://reactjs.org/docs/render-props.html), `children`, to handle the subscribed value when it changes:
39
-
40
-```js
41
-<Subscription source={eventDispatcher}>
42
- {value => <AnotherComponent value={value} />}
43
-</Subscription>
44
-```
45
-
46
-# Examples
47
-
48
-This API can be used to subscribe to a variety of "subscribable" sources, from event dispatchers to RxJS observables. Below are a few examples of how to subscribe to common types.
49
-
50
-## Subscribing to event dispatchers
51
-
52
-Below is an example showing how `create-subscription` can be used to subscribe to event dispatchers such as DOM elements.
53
-
54
-```js
55
-import React from "react";
56
-import { createSubscription } from "create-subscription";
57
-
58
-// Start with a simple component.
59
-// In this case, it's a function component, but it could have been a class.
60
-function FollowerComponent({ followersCount }) {
61
- return <div>You have {followersCount} followers!</div>;
62
-}
63
-
64
-// Create a wrapper component to manage the subscription.
65
-const EventHandlerSubscription = createSubscription({
66
- getCurrentValue: eventDispatcher => eventDispatcher.value,
67
- subscribe: (eventDispatcher, callback) => {
68
- const onChange = event => callback(eventDispatcher.value);
69
- eventDispatcher.addEventListener("change", onChange);
70
- return () => eventDispatcher.removeEventListener("change", onChange);
71
- }
72
-});
73
-
74
-// Your component can now be used as shown below.
75
-// In this example, 'eventDispatcher' represents a generic event dispatcher.
76
-<EventHandlerSubscription source={eventDispatcher}>
77
- {value => <FollowerComponent followersCount={value} />}
78
-</EventHandlerSubscription>;
79
-```
80
-
81
-## Subscribing to observables
82
-
83
-Below are examples showing how `create-subscription` can be used to subscribe to certain types of observables (e.g. RxJS `BehaviorSubject` and `ReplaySubject`).
84
-
85
-**Note** that it is not possible to support all observable types (e.g. RxJS `Subject` or `Observable`) because some provide no way to read the "current" value after it has been emitted.
86
-
87
-### `BehaviorSubject`
88
-```js
89
-const BehaviorSubscription = createSubscription({
90
- getCurrentValue: behaviorSubject => behaviorSubject.getValue(),
91
- subscribe: (behaviorSubject, callback) => {
92
- const subscription = behaviorSubject.subscribe(callback);
93
- return () => subscription.unsubscribe();
94
- }
95
-});
96
-```
97
-
98
-### `ReplaySubject`
99
-```js
100
-const ReplaySubscription = createSubscription({
101
- getCurrentValue: replaySubject => {
102
- let currentValue;
103
- // ReplaySubject does not have a sync data getter,
104
- // So we need to temporarily subscribe to retrieve the most recent value.
105
- replaySubject
106
- .subscribe(value => {
107
- currentValue = value;
108
- })
109
- .unsubscribe();
110
- return currentValue;
111
- },
112
- subscribe: (replaySubject, callback) => {
113
- const subscription = replaySubject.subscribe(callback);
114
- return () => subscription.unsubscribe();
115
- }
116
-});
117
-```
118
-
119
-## Subscribing to a Promise
120
-
121
-Below is an example showing how `create-subscription` can be used with native Promises.
122
-
123
-**Note** that an initial render value of `undefined` is unavoidable due to the fact that Promises provide no way to synchronously read their current value.
124
-
125
-**Note** the lack of a way to "unsubscribe" from a Promise can result in memory leaks as long as something has a reference to the Promise. This should be taken into consideration when determining whether Promises are appropriate to use in this way within your application.
126
-
127
-```js
128
-import React from "react";
129
-import { createSubscription } from "create-subscription";
130
-
131
-// Start with a simple component.
132
-function LoadingComponent({ loadingStatus }) {
133
- if (loadingStatus === undefined) {
134
- // Loading
135
- } else if (loadingStatus === null) {
136
- // Error
137
- } else {
138
- // Success
139
- }
140
-}
141
-
142
-// Wrap the function component with a subscriber HOC.
143
-// This HOC will manage subscriptions and pass values to the decorated component.
144
-// It will add and remove subscriptions in an async-safe way when props change.
145
-const PromiseSubscription = createSubscription({
146
- getCurrentValue: promise => {
147
- // There is no way to synchronously read a Promise's value,
148
- // So this method should return undefined.
149
- return undefined;
150
- },
151
- subscribe: (promise, callback) => {
152
- promise.then(
153
- // Success
154
- value => callback(value),
155
- // Failure
156
- () => callback(null)
157
- );
158
-
159
- // There is no way to "unsubscribe" from a Promise.
160
- // create-subscription will still prevent stale values from rendering.
161
- return () => {};
162
- }
163
-});
164
-
165
-// Your component can now be used as shown below.
166
-<PromiseSubscription source={loadingPromise}>
167
- {loadingStatus => <LoadingComponent loadingStatus={loadingStatus} />}
168
-</PromiseSubscription>
169
-```
packages/create-subscription/index.js
deleted
-12
@@ -1,12 +0,0 @@
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
- * @flow
8
- */
9
-
10
-'use strict';
11
-
12
-export * from './src/createSubscription';
packages/create-subscription/npm/index.js
deleted
-7
@@ -1,7 +0,0 @@
1
-'use strict';
2
-
3
-if (process.env.NODE_ENV === 'production') {
4
- module.exports = require('./cjs/create-subscription.production.min.js');
5
-} else {
6
- module.exports = require('./cjs/create-subscription.development.js');
7
-}
packages/create-subscription/package.json
deleted
-22
@@ -1,22 +0,0 @@
1
-{
2
- "name": "create-subscription",
3
- "description": "utility for subscribing to external data sources inside React components",
4
- "version": "18.0.0",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/facebook/react.git",
8
- "directory": "packages/create-subscription"
9
- },
10
- "files": [
11
- "LICENSE",
12
- "README.md",
13
- "index.js",
14
- "cjs/"
15
- ],
16
- "peerDependencies": {
17
- "react": "^18.0.0"
18
- },
19
- "devDependencies": {
20
- "rxjs": "^5.5.6"
21
- }
22
-}
packages/create-subscription/src/__tests__/createSubscription-test.js
deleted
-505
@@ -1,505 +0,0 @@
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 react-core
8
- */
9
-
10
-'use strict';
11
-
12
-let createSubscription;
13
-let BehaviorSubject;
14
-let React;
15
-let ReactNoop;
16
-let Scheduler;
17
-let ReplaySubject;
18
-
19
-describe('createSubscription', () => {
20
- beforeEach(() => {
21
- jest.resetModules();
22
- createSubscription = require('create-subscription').createSubscription;
23
-
24
- React = require('react');
25
- ReactNoop = require('react-noop-renderer');
26
- Scheduler = require('scheduler');
27
-
28
- BehaviorSubject = require('rxjs/BehaviorSubject').BehaviorSubject;
29
- ReplaySubject = require('rxjs/ReplaySubject').ReplaySubject;
30
- });
31
-
32
- function createBehaviorSubject(initialValue) {
33
- const behaviorSubject = new BehaviorSubject();
34
- if (initialValue) {
35
- behaviorSubject.next(initialValue);
36
- }
37
- return behaviorSubject;
38
- }
39
-
40
- function createReplaySubject(initialValue) {
41
- const replaySubject = new ReplaySubject();
42
- if (initialValue) {
43
- replaySubject.next(initialValue);
44
- }
45
- return replaySubject;
46
- }
47
-
48
- it('supports basic subscription pattern', () => {
49
- const Subscription = createSubscription({
50
- getCurrentValue: source => source.getValue(),
51
- subscribe: (source, callback) => {
52
- const subscription = source.subscribe(callback);
53
- return () => subscription.unsubscribe;
54
- },
55
- });
56
-
57
- const observable = createBehaviorSubject();
58
- ReactNoop.render(
59
- <Subscription source={observable}>
60
- {(value = 'default') => {
61
- Scheduler.unstable_yieldValue(value);
62
- return null;
63
- }}
64
- </Subscription>,
65
- );
66
-
67
- // Updates while subscribed should re-render the child component
68
- expect(Scheduler).toFlushAndYield(['default']);
69
- observable.next(123);
70
- expect(Scheduler).toFlushAndYield([123]);
71
- observable.next('abc');
72
- expect(Scheduler).toFlushAndYield(['abc']);
73
-
74
- // Unmounting the subscriber should remove listeners
75
- ReactNoop.render(<div />);
76
- observable.next(456);
77
- expect(Scheduler).toFlushAndYield([]);
78
- });
79
-
80
- it('should support observable types like RxJS ReplaySubject', () => {
81
- const Subscription = createSubscription({
82
- getCurrentValue: source => {
83
- let currentValue;
84
- source
85
- .subscribe(value => {
86
- currentValue = value;
87
- })
88
- .unsubscribe();
89
- return currentValue;
90
- },
91
- subscribe: (source, callback) => {
92
- const subscription = source.subscribe(callback);
93
- return () => subscription.unsubscribe;
94
- },
95
- });
96
-
97
- function render(value = 'default') {
98
- Scheduler.unstable_yieldValue(value);
99
- return null;
100
- }
101
-
102
- const observable = createReplaySubject('initial');
103
-
104
- ReactNoop.render(<Subscription source={observable}>{render}</Subscription>);
105
- expect(Scheduler).toFlushAndYield(['initial']);
106
- observable.next('updated');
107
- expect(Scheduler).toFlushAndYield(['updated']);
108
-
109
- // Unsetting the subscriber prop should reset subscribed values
110
- ReactNoop.render(<Subscription>{render}</Subscription>);
111
- expect(Scheduler).toFlushAndYield(['default']);
112
- });
113
-
114
- describe('Promises', () => {
115
- it('should support Promises', async () => {
116
- const Subscription = createSubscription({
117
- getCurrentValue: source => undefined,
118
- subscribe: (source, callback) => {
119
- source.then(
120
- value => callback(value),
121
- value => callback(value),
122
- );
123
- // (Can't unsubscribe from a Promise)
124
- return () => {};
125
- },
126
- });
127
-
128
- function render(hasLoaded) {
129
- if (hasLoaded === undefined) {
130
- Scheduler.unstable_yieldValue('loading');
131
- } else {
132
- Scheduler.unstable_yieldValue(hasLoaded ? 'finished' : 'failed');
133
- }
134
- return null;
135
- }
136
-
137
- let resolveA, rejectB;
138
- const promiseA = new Promise((resolve, reject) => {
139
- resolveA = resolve;
140
- });
141
- const promiseB = new Promise((resolve, reject) => {
142
- rejectB = reject;
143
- });
144
-
145
- // Test a promise that resolves after render
146
- ReactNoop.render(<Subscription source={promiseA}>{render}</Subscription>);
147
- expect(Scheduler).toFlushAndYield(['loading']);
148
- resolveA(true);
149
- await promiseA;
150
- expect(Scheduler).toFlushAndYield(['finished']);
151
-
152
- // Test a promise that resolves before render
153
- // Note that this will require an extra render anyway,
154
- // Because there is no way to synchronously get a Promise's value
155
- rejectB(false);
156
- ReactNoop.render(<Subscription source={promiseB}>{render}</Subscription>);
157
- expect(Scheduler).toFlushAndYield(['loading']);
158
- await promiseB.catch(() => true);
159
- expect(Scheduler).toFlushAndYield(['failed']);
160
- });
161
-
162
- it('should still work if unsubscription is managed incorrectly', async () => {
163
- const Subscription = createSubscription({
164
- getCurrentValue: source => undefined,
165
- subscribe: (source, callback) => {
166
- source.then(callback);
167
- // (Can't unsubscribe from a Promise)
168
- return () => {};
169
- },
170
- });
171
-
172
- function render(value = 'default') {
173
- Scheduler.unstable_yieldValue(value);
174
- return null;
175
- }
176
-
177
- let resolveA, resolveB;
178
- const promiseA = new Promise(resolve => (resolveA = resolve));
179
- const promiseB = new Promise(resolve => (resolveB = resolve));
180
-
181
- // Subscribe first to Promise A then Promise B
182
- ReactNoop.render(<Subscription source={promiseA}>{render}</Subscription>);
183
- expect(Scheduler).toFlushAndYield(['default']);
184
- ReactNoop.render(<Subscription source={promiseB}>{render}</Subscription>);
185
- expect(Scheduler).toFlushAndYield(['default']);
186
-
187
- // Resolve both Promises
188
- resolveB(123);
189
- resolveA('abc');
190
- await Promise.all([promiseA, promiseB]);
191
-
192
- // Ensure that only Promise B causes an update
193
- expect(Scheduler).toFlushAndYield([123]);
194
- });
195
-
196
- it('should not call setState for a Promise that resolves after unmount', async () => {
197
- const Subscription = createSubscription({
198
- getCurrentValue: source => undefined,
199
- subscribe: (source, callback) => {
200
- source.then(
201
- value => callback(value),
202
- value => callback(value),
203
- );
204
- // (Can't unsubscribe from a Promise)
205
- return () => {};
206
- },
207
- });
208
-
209
- function render(hasLoaded) {
210
- Scheduler.unstable_yieldValue('rendered');
211
- return null;
212
- }
213
-
214
- let resolvePromise;
215
- const promise = new Promise((resolve, reject) => {
216
- resolvePromise = resolve;
217
- });
218
-
219
- ReactNoop.render(<Subscription source={promise}>{render}</Subscription>);
220
- expect(Scheduler).toFlushAndYield(['rendered']);
221
-
222
- // Unmount
223
- ReactNoop.render(null);
224
- expect(Scheduler).toFlushWithoutYielding();
225
-
226
- // Resolve Promise should not trigger a setState warning
227
- resolvePromise(true);
228
- await promise;
229
- });
230
- });
231
-
232
- it('should unsubscribe from old subscribables and subscribe to new subscribables when props change', () => {
233
- const Subscription = createSubscription({
234
- getCurrentValue: source => source.getValue(),
235
- subscribe: (source, callback) => {
236
- const subscription = source.subscribe(callback);
237
- return () => subscription.unsubscribe();
238
- },
239
- });
240
-
241
- function render(value = 'default') {
242
- Scheduler.unstable_yieldValue(value);
243
- return null;
244
- }
245
-
246
- const observableA = createBehaviorSubject('a-0');
247
- const observableB = createBehaviorSubject('b-0');
248
-
249
- ReactNoop.render(
250
- <Subscription source={observableA}>{render}</Subscription>,
251
- );
252
-
253
- // Updates while subscribed should re-render the child component
254
- expect(Scheduler).toFlushAndYield(['a-0']);
255
-
256
- // Unsetting the subscriber prop should reset subscribed values
257
- ReactNoop.render(
258
- <Subscription source={observableB}>{render}</Subscription>,
259
- );
260
- expect(Scheduler).toFlushAndYield(['b-0']);
261
-
262
- // Updates to the old subscribable should not re-render the child component
263
- observableA.next('a-1');
264
- expect(Scheduler).toFlushAndYield([]);
265
-
266
- // Updates to the bew subscribable should re-render the child component
267
- observableB.next('b-1');
268
- expect(Scheduler).toFlushAndYield(['b-1']);
269
- });
270
-
271
- it('should ignore values emitted by a new subscribable until the commit phase', () => {
272
- const log = [];
273
-
274
- function Child({value}) {
275
- Scheduler.unstable_yieldValue('Child: ' + value);
276
- return null;
277
- }
278
-
279
- const Subscription = createSubscription({
280
- getCurrentValue: source => source.getValue(),
281
- subscribe: (source, callback) => {
282
- const subscription = source.subscribe(callback);
283
- return () => subscription.unsubscribe();
284
- },
285
- });
286
-
287
- class Parent extends React.Component {
288
- state = {};
289
-
290
- static getDerivedStateFromProps(nextProps, prevState) {
291
- if (nextProps.observed !== prevState.observed) {
292
- return {
293
- observed: nextProps.observed,
294
- };
295
- }
296
-
297
- return null;
298
- }
299
-
300
- componentDidMount() {
301
- log.push('Parent.componentDidMount');
302
- }
303
-
304
- componentDidUpdate() {
305
- log.push('Parent.componentDidUpdate');
306
- }
307
-
308
- render() {
309
- return (
310
- <Subscription source={this.state.observed}>
311
- {(value = 'default') => {
312
- Scheduler.unstable_yieldValue('Subscriber: ' + value);
313
- return <Child value={value} />;
314
- }}
315
- </Subscription>
316
- );
317
- }
318
- }
319
-
320
- const observableA = createBehaviorSubject('a-0');
321
- const observableB = createBehaviorSubject('b-0');
322
-
323
- ReactNoop.render(<Parent observed={observableA} />);
324
- expect(Scheduler).toFlushAndYield(['Subscriber: a-0', 'Child: a-0']);
325
- expect(log).toEqual(['Parent.componentDidMount']);
326
-
327
- // Start React update, but don't finish
328
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
329
- React.startTransition(() => {
330
- ReactNoop.render(<Parent observed={observableB} />);
331
- });
332
- } else {
333
- ReactNoop.render(<Parent observed={observableB} />);
334
- }
335
- expect(Scheduler).toFlushAndYieldThrough(['Subscriber: b-0']);
336
- expect(log).toEqual(['Parent.componentDidMount']);
337
-
338
- // Emit some updates from the uncommitted subscribable
339
- observableB.next('b-1');
340
- observableB.next('b-2');
341
- observableB.next('b-3');
342
-
343
- // Update again
344
- ReactNoop.render(<Parent observed={observableA} />);
345
-
346
- // Flush everything and ensure that the correct subscribable is used
347
- // We expect the last emitted update to be rendered (because of the commit phase value check)
348
- // But the intermediate ones should be ignored,
349
- // And the final rendered output should be the higher-priority observable.
350
- expect(Scheduler).toFlushAndYield([
351
- 'Child: b-0',
352
- 'Subscriber: b-3',
353
- 'Child: b-3',
354
- 'Subscriber: a-0',
355
- 'Child: a-0',
356
- ]);
357
- expect(log).toEqual([
358
- 'Parent.componentDidMount',
359
- 'Parent.componentDidUpdate',
360
- 'Parent.componentDidUpdate',
361
- ]);
362
- });
363
-
364
- it('should not drop values emitted between updates', () => {
365
- const log = [];
366
-
367
- function Child({value}) {
368
- Scheduler.unstable_yieldValue('Child: ' + value);
369
- return null;
370
- }
371
-
372
- const Subscription = createSubscription({
373
- getCurrentValue: source => source.getValue(),
374
- subscribe: (source, callback) => {
375
- const subscription = source.subscribe(callback);
376
- return () => subscription.unsubscribe();
377
- },
378
- });
379
-
380
- class Parent extends React.Component {
381
- state = {};
382
-
383
- static getDerivedStateFromProps(nextProps, prevState) {
384
- if (nextProps.observed !== prevState.observed) {
385
- return {
386
- observed: nextProps.observed,
387
- };
388
- }
389
-
390
- return null;
391
- }
392
-
393
- componentDidMount() {
394
- log.push('Parent.componentDidMount');
395
- }
396
-
397
- componentDidUpdate() {
398
- log.push('Parent.componentDidUpdate');
399
- }
400
-
401
- render() {
402
- return (
403
- <Subscription source={this.state.observed}>
404
- {(value = 'default') => {
405
- Scheduler.unstable_yieldValue('Subscriber: ' + value);
406
- return <Child value={value} />;
407
- }}
408
- </Subscription>
409
- );
410
- }
411
- }
412
-
413
- const observableA = createBehaviorSubject('a-0');
414
- const observableB = createBehaviorSubject('b-0');
415
-
416
- ReactNoop.render(<Parent observed={observableA} />);
417
- expect(Scheduler).toFlushAndYield(['Subscriber: a-0', 'Child: a-0']);
418
- expect(log).toEqual(['Parent.componentDidMount']);
419
-
420
- // Start React update, but don't finish
421
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
422
- React.startTransition(() => {
423
- ReactNoop.render(<Parent observed={observableB} />);
424
- });
425
- } else {
426
- ReactNoop.render(<Parent observed={observableB} />);
427
- }
428
- expect(Scheduler).toFlushAndYieldThrough(['Subscriber: b-0']);
429
- expect(log).toEqual(['Parent.componentDidMount']);
430
-
431
- // Emit some updates from the old subscribable
432
- observableA.next('a-1');
433
- observableA.next('a-2');
434
-
435
- // Update again
436
- ReactNoop.render(<Parent observed={observableA} />);
437
-
438
- // Flush everything and ensure that the correct subscribable is used
439
- // We expect the new subscribable to finish rendering,
440
- // But then the updated values from the old subscribable should be used.
441
- expect(Scheduler).toFlushAndYield([
442
- 'Child: b-0',
443
- 'Subscriber: a-2',
444
- 'Child: a-2',
445
- ]);
446
- expect(log).toEqual([
447
- 'Parent.componentDidMount',
448
- 'Parent.componentDidUpdate',
449
- 'Parent.componentDidUpdate',
450
- ]);
451
-
452
- // Updates from the new subscribable should be ignored.
453
- observableB.next('b-1');
454
- expect(Scheduler).toFlushAndYield([]);
455
- expect(log).toEqual([
456
- 'Parent.componentDidMount',
457
- 'Parent.componentDidUpdate',
458
- 'Parent.componentDidUpdate',
459
- ]);
460
- });
461
-
462
- describe('warnings', () => {
463
- it('should warn for invalid missing getCurrentValue', () => {
464
- expect(() => {
465
- createSubscription(
466
- {
467
- subscribe: () => () => {},
468
- },
469
- () => null,
470
- );
471
- }).toErrorDev('Subscription must specify a getCurrentValue function', {
472
- withoutStack: true,
473
- });
474
- });
475
-
476
- it('should warn for invalid missing subscribe', () => {
477
- expect(() => {
478
- createSubscription(
479
- {
480
- getCurrentValue: () => () => {},
481
- },
482
- () => null,
483
- );
484
- }).toErrorDev('Subscription must specify a subscribe function', {
485
- withoutStack: true,
486
- });
487
- });
488
-
489
- it('should warn if subscribe does not return an unsubscribe method', () => {
490
- const Subscription = createSubscription({
491
- getCurrentValue: source => undefined,
492
- subscribe: (source, callback) => {},
493
- });
494
-
495
- const observable = createBehaviorSubject();
496
- ReactNoop.render(
497
- <Subscription source={observable}>{value => null}</Subscription>,
498
- );
499
-
500
- expect(Scheduler).toFlushAndThrow(
501
- 'A subscription must return an unsubscribe function.',
502
- );
503
- });
504
- });
505
-});
packages/create-subscription/src/createSubscription.js
deleted
-159
@@ -1,159 +0,0 @@
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
- * @flow
8
- */
9
-
10
-import * as React from 'react';
11
-
12
-type Unsubscribe = () => void;
13
-
14
-export function createSubscription<Property, Value>(
15
- config: $ReadOnly<{|
16
- // Synchronously gets the value for the subscribed property.
17
- // Return undefined if the subscribable value is undefined,
18
- // Or does not support synchronous reading (e.g. native Promise).
19
- getCurrentValue: (source: Property) => Value | void,
20
-
21
- // Setup a subscription for the subscribable value in props, and return an unsubscribe function.
22
- // Return empty function if the property cannot be unsubscribed from (e.g. native Promises).
23
- // Due to the variety of change event types, subscribers should provide their own handlers.
24
- // Those handlers should not attempt to update state though;
25
- // They should call the callback() instead when a subscription changes.
26
- subscribe: (
27
- source: Property,
28
- callback: (value: Value | void) => void,
29
- ) => Unsubscribe,
30
- |}>,
31
-): React$ComponentType<{|
32
- children: (value: Value | void) => React$Node,
33
- source: Property,
34
-|}> {
35
- const {getCurrentValue, subscribe} = config;
36
-
37
- if (__DEV__) {
38
- if (typeof getCurrentValue !== 'function') {
39
- console.error('Subscription must specify a getCurrentValue function');
40
- }
41
- if (typeof subscribe !== 'function') {
42
- console.error('Subscription must specify a subscribe function');
43
- }
44
- }
45
-
46
- type Props = {|
47
- children: (value: Value) => React$Element<any>,
48
- source: Property,
49
- |};
50
- type State = {|
51
- source: Property,
52
- value: Value | void,
53
- |};
54
-
55
- // Reference: https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3
56
- class Subscription extends React.Component<Props, State> {
57
- state: State = {
58
- source: this.props.source,
59
- value:
60
- this.props.source != null
61
- ? getCurrentValue(this.props.source)
62
- : undefined,
63
- };
64
-
65
- _hasUnmounted: boolean = false;
66
- _unsubscribe: Unsubscribe | null = null;
67
-
68
- static getDerivedStateFromProps(nextProps, prevState) {
69
- if (nextProps.source !== prevState.source) {
70
- return {
71
- source: nextProps.source,
72
- value:
73
- nextProps.source != null
74
- ? getCurrentValue(nextProps.source)
75
- : undefined,
76
- };
77
- }
78
-
79
- return null;
80
- }
81
-
82
- componentDidMount() {
83
- this.subscribe();
84
- }
85
-
86
- componentDidUpdate(prevProps, prevState) {
87
- if (this.state.source !== prevState.source) {
88
- this.unsubscribe();
89
- this.subscribe();
90
- }
91
- }
92
-
93
- componentWillUnmount() {
94
- this.unsubscribe();
95
-
96
- // Track mounted to avoid calling setState after unmounting
97
- // For source like Promises that can't be unsubscribed from.
98
- this._hasUnmounted = true;
99
- }
100
-
101
- render() {
102
- return this.props.children(this.state.value);
103
- }
104
-
105
- subscribe() {
106
- const {source} = this.state;
107
- if (source != null) {
108
- const callback = (value: Value | void) => {
109
- if (this._hasUnmounted) {
110
- return;
111
- }
112
-
113
- this.setState(state => {
114
- // If the value is the same, skip the unnecessary state update.
115
- if (value === state.value) {
116
- return null;
117
- }
118
-
119
- // If this event belongs to an old or uncommitted data source, ignore it.
120
- if (source !== state.source) {
121
- return null;
122
- }
123
-
124
- return {value};
125
- });
126
- };
127
-
128
- // Store the unsubscribe method for later (in case the subscribable prop changes).
129
- const unsubscribe = subscribe(source, callback);
130
-
131
- if (typeof unsubscribe !== 'function') {
132
- throw new Error(
133
- 'A subscription must return an unsubscribe function.',
134
- );
135
- }
136
-
137
- // It's safe to store unsubscribe on the instance because
138
- // We only read or write that property during the "commit" phase.
139
- this._unsubscribe = unsubscribe;
140
-
141
- // External values could change between render and mount,
142
- // In some cases it may be important to handle this case.
143
- const value = getCurrentValue(this.props.source);
144
- if (value !== this.state.value) {
145
- this.setState({value});
146
- }
147
- }
148
- }
149
-
150
- unsubscribe() {
151
- if (typeof this._unsubscribe === 'function') {
152
- this._unsubscribe();
153
- }
154
- this._unsubscribe = null;
155
- }
156
- }
157
-
158
- return Subscription;
159
-}
scripts/rollup/bundles.js
-17
@@ -772,23 +772,6 @@ const bundles = [
772
externals: ['react', 'scheduler'],
773
},
774
775
- /******* createComponentWithSubscriptions *******/
776
- {
777
- bundleTypes: [NODE_DEV, NODE_PROD],
778
- moduleType: ISOMORPHIC,
779
- entry: 'create-subscription',
780
- global: 'createSubscription',
781
- externals: ['react'],
782
- minifyWithProdErrorCodes: true,
783
- wrapWithModuleBoundaries: true,
784
- babel: opts =>
785
- Object.assign({}, opts, {
786
- plugins: opts.plugins.concat([
787
- [require.resolve('@babel/plugin-transform-classes'), {loose: true}],
788
- ]),
789
- }),
790
- },
791
-
775
/******* Hook for managing subscriptions safely *******/
776
{
777
bundleTypes: [NODE_DEV, NODE_PROD],