Devtools/function context change (#22047)
Byron Luk committed
Aug 10, 2021 at 11:16 UTC
da627ded867c882290776b4477250b6b312b77b4
4 files changed
+396
-62
packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
+180
-31
@@ -2485,7 +2485,7 @@ Object {
2485
exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 0 1`] = `
2486
Object {
2487
"changeDescriptions": Map {
2488
- 3 => Object {
2488
+ 4 => Object {
2489
"context": null,
2490
"didHooksChange": false,
2491
"isFirstMount": true,
@@ -2499,11 +2499,13 @@ Object {
2499
1 => 0,
2500
2 => 0,
2501
3 => 0,
2502
+ 4 => 0,
2503
},
2504
"fiberSelfDurations": Map {
2505
1 => 0,
2506
2 => 0,
2507
3 => 0,
2508
+ 4 => 0,
2509
},
2510
"passiveEffectDuration": 0,
2511
"priorityLevel": "Immediate",
@@ -2523,8 +2525,8 @@ Object {
2525
exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 1 1`] = `
2526
Object {
2527
"changeDescriptions": Map {
2526
- 3 => Object {
2527
- "context": null,
2528
+ 4 => Object {
2529
+ "context": false,
2530
"didHooksChange": false,
2531
"isFirstMount": false,
2532
"props": Array [
@@ -2536,11 +2538,13 @@ Object {
2538
"duration": 0,
2539
"effectDuration": null,
2540
"fiberActualDurations": Map {
2541
+ 4 => 0,
2542
3 => 0,
2543
2 => 0,
2544
1 => 0,
2545
},
2546
"fiberSelfDurations": Map {
2547
+ 4 => 0,
2548
3 => 0,
2549
2 => 0,
2550
1 => 0,
@@ -2563,8 +2567,8 @@ Object {
2567
exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 2 1`] = `
2568
Object {
2569
"changeDescriptions": Map {
2566
- 3 => Object {
2567
- "context": null,
2570
+ 4 => Object {
2571
+ "context": false,
2572
"didHooksChange": true,
2573
"isFirstMount": false,
2574
"props": Array [],
@@ -2574,10 +2578,10 @@ Object {
2578
"duration": 0,
2579
"effectDuration": null,
2580
"fiberActualDurations": Map {
2577
- 3 => 0,
2581
+ 4 => 0,
2582
},
2583
"fiberSelfDurations": Map {
2580
- 3 => 0,
2584
+ 4 => 0,
2585
},
2586
"passiveEffectDuration": null,
2587
"priorityLevel": "Immediate",
@@ -2586,7 +2590,7 @@ Object {
2590
Object {
2591
"displayName": "Component",
2592
"hocDisplayNames": null,
2589
- "id": 3,
2593
+ "id": 4,
2594
"key": null,
2595
"type": 5,
2596
},
@@ -2597,8 +2601,8 @@ Object {
2601
exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 3 1`] = `
2602
Object {
2603
"changeDescriptions": Map {
2600
- 3 => Object {
2601
- "context": null,
2604
+ 4 => Object {
2605
+ "context": false,
2606
"didHooksChange": true,
2607
"isFirstMount": false,
2608
"props": Array [],
@@ -2608,10 +2612,10 @@ Object {
2612
"duration": 0,
2613
"effectDuration": null,
2614
"fiberActualDurations": Map {
2611
- 3 => 0,
2615
+ 4 => 0,
2616
},
2617
"fiberSelfDurations": Map {
2614
- 3 => 0,
2618
+ 4 => 0,
2619
},
2620
"passiveEffectDuration": null,
2621
"priorityLevel": "Immediate",
@@ -2620,7 +2624,7 @@ Object {
2624
Object {
2625
"displayName": "Component",
2626
"hocDisplayNames": null,
2623
- "id": 3,
2627
+ "id": 4,
2628
"key": null,
2629
"type": 5,
2630
},
@@ -2631,8 +2635,48 @@ Object {
2635
exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 4 1`] = `
2636
Object {
2637
"changeDescriptions": Map {
2634
- 3 => Object {
2635
- "context": null,
2638
+ 4 => Object {
2639
+ "context": true,
2640
+ "didHooksChange": false,
2641
+ "isFirstMount": false,
2642
+ "props": Array [],
2643
+ "state": null,
2644
+ },
2645
+ },
2646
+ "duration": 0,
2647
+ "effectDuration": null,
2648
+ "fiberActualDurations": Map {
2649
+ 4 => 0,
2650
+ 3 => 0,
2651
+ 2 => 0,
2652
+ 1 => 0,
2653
+ },
2654
+ "fiberSelfDurations": Map {
2655
+ 4 => 0,
2656
+ 3 => 0,
2657
+ 2 => 0,
2658
+ 1 => 0,
2659
+ },
2660
+ "passiveEffectDuration": null,
2661
+ "priorityLevel": "Immediate",
2662
+ "timestamp": 0,
2663
+ "updaters": Array [
2664
+ Object {
2665
+ "displayName": "Anonymous",
2666
+ "hocDisplayNames": null,
2667
+ "id": 1,
2668
+ "key": null,
2669
+ "type": 11,
2670
+ },
2671
+ ],
2672
+}
2673
+`;
2674
+
2675
+exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 5 1`] = `
2676
+Object {
2677
+ "changeDescriptions": Map {
2678
+ 4 => Object {
2679
+ "context": true,
2680
"didHooksChange": false,
2681
"isFirstMount": false,
2682
"props": Array [],
@@ -2642,11 +2686,13 @@ Object {
2686
"duration": 0,
2687
"effectDuration": null,
2688
"fiberActualDurations": Map {
2689
+ 4 => 0,
2690
3 => 0,
2691
2 => 0,
2692
1 => 0,
2693
},
2694
"fiberSelfDurations": Map {
2695
+ 4 => 0,
2696
3 => 0,
2697
2 => 0,
2698
1 => 0,
@@ -2674,7 +2720,7 @@ Object {
2720
Object {
2721
"changeDescriptions": Array [
2722
Array [
2677
- 3,
2723
+ 4,
2724
Object {
2725
"context": null,
2726
"didHooksChange": false,
@@ -2699,6 +2745,10 @@ Object {
2745
3,
2746
0,
2747
],
2748
+ Array [
2749
+ 4,
2750
+ 0,
2751
+ ],
2752
],
2753
"fiberSelfDurations": Array [
2754
Array [
@@ -2713,6 +2763,10 @@ Object {
2763
3,
2764
0,
2765
],
2766
+ Array [
2767
+ 4,
2768
+ 0,
2769
+ ],
2770
],
2771
"passiveEffectDuration": 0,
2772
"priorityLevel": "Immediate",
@@ -2730,9 +2784,9 @@ Object {
2784
Object {
2785
"changeDescriptions": Array [
2786
Array [
2733
- 3,
2787
+ 4,
2788
Object {
2735
- "context": null,
2789
+ "context": false,
2790
"didHooksChange": false,
2791
"isFirstMount": false,
2792
"props": Array [
@@ -2745,6 +2799,10 @@ Object {
2799
"duration": 0,
2800
"effectDuration": null,
2801
"fiberActualDurations": Array [
2802
+ Array [
2803
+ 4,
2804
+ 0,
2805
+ ],
2806
Array [
2807
3,
2808
0,
@@ -2759,6 +2817,10 @@ Object {
2817
],
2818
],
2819
"fiberSelfDurations": Array [
2820
+ Array [
2821
+ 4,
2822
+ 0,
2823
+ ],
2824
Array [
2825
3,
2826
0,
@@ -2788,9 +2850,9 @@ Object {
2850
Object {
2851
"changeDescriptions": Array [
2852
Array [
2791
- 3,
2853
+ 4,
2854
Object {
2793
- "context": null,
2855
+ "context": false,
2856
"didHooksChange": true,
2857
"isFirstMount": false,
2858
"props": Array [],
@@ -2802,13 +2864,13 @@ Object {
2864
"effectDuration": null,
2865
"fiberActualDurations": Array [
2866
Array [
2805
- 3,
2867
+ 4,
2868
0,
2869
],
2870
],
2871
"fiberSelfDurations": Array [
2872
Array [
2811
- 3,
2873
+ 4,
2874
0,
2875
],
2876
],
@@ -2819,7 +2881,7 @@ Object {
2881
Object {
2882
"displayName": "Component",
2883
"hocDisplayNames": null,
2822
- "id": 3,
2884
+ "id": 4,
2885
"key": null,
2886
"type": 5,
2887
},
@@ -2828,9 +2890,9 @@ Object {
2890
Object {
2891
"changeDescriptions": Array [
2892
Array [
2831
- 3,
2893
+ 4,
2894
Object {
2833
- "context": null,
2895
+ "context": false,
2896
"didHooksChange": true,
2897
"isFirstMount": false,
2898
"props": Array [],
@@ -2842,13 +2904,13 @@ Object {
2904
"effectDuration": null,
2905
"fiberActualDurations": Array [
2906
Array [
2845
- 3,
2907
+ 4,
2908
0,
2909
],
2910
],
2911
"fiberSelfDurations": Array [
2912
Array [
2851
- 3,
2913
+ 4,
2914
0,
2915
],
2916
],
@@ -2859,7 +2921,7 @@ Object {
2921
Object {
2922
"displayName": "Component",
2923
"hocDisplayNames": null,
2862
- "id": 3,
2924
+ "id": 4,
2925
"key": null,
2926
"type": 5,
2927
},
@@ -2867,10 +2929,74 @@ Object {
2929
},
2930
Object {
2931
"changeDescriptions": Array [
2932
+ Array [
2933
+ 4,
2934
+ Object {
2935
+ "context": true,
2936
+ "didHooksChange": false,
2937
+ "isFirstMount": false,
2938
+ "props": Array [],
2939
+ "state": null,
2940
+ },
2941
+ ],
2942
+ ],
2943
+ "duration": 0,
2944
+ "effectDuration": null,
2945
+ "fiberActualDurations": Array [
2946
+ Array [
2947
+ 4,
2948
+ 0,
2949
+ ],
2950
+ Array [
2951
+ 3,
2952
+ 0,
2953
+ ],
2954
+ Array [
2955
+ 2,
2956
+ 0,
2957
+ ],
2958
+ Array [
2959
+ 1,
2960
+ 0,
2961
+ ],
2962
+ ],
2963
+ "fiberSelfDurations": Array [
2964
+ Array [
2965
+ 4,
2966
+ 0,
2967
+ ],
2968
Array [
2969
3,
2970
+ 0,
2971
+ ],
2972
+ Array [
2973
+ 2,
2974
+ 0,
2975
+ ],
2976
+ Array [
2977
+ 1,
2978
+ 0,
2979
+ ],
2980
+ ],
2981
+ "passiveEffectDuration": null,
2982
+ "priorityLevel": "Immediate",
2983
+ "timestamp": 0,
2984
+ "updaters": Array [
2985
+ Object {
2986
+ "displayName": "Anonymous",
2987
+ "hocDisplayNames": null,
2988
+ "id": 1,
2989
+ "key": null,
2990
+ "type": 11,
2991
+ },
2992
+ ],
2993
+ },
2994
+ Object {
2995
+ "changeDescriptions": Array [
2996
+ Array [
2997
+ 4,
2998
Object {
2873
- "context": null,
2999
+ "context": true,
3000
"didHooksChange": false,
3001
"isFirstMount": false,
3002
"props": Array [],
@@ -2881,6 +3007,10 @@ Object {
3007
"duration": 0,
3008
"effectDuration": null,
3009
"fiberActualDurations": Array [
3010
+ Array [
3011
+ 4,
3012
+ 0,
3013
+ ],
3014
Array [
3015
3,
3016
0,
@@ -2895,6 +3025,10 @@ Object {
3025
],
3026
],
3027
"fiberSelfDurations": Array [
3028
+ Array [
3029
+ 4,
3030
+ 0,
3031
+ ],
3032
Array [
3033
3,
3034
0,
@@ -2973,14 +3107,29 @@ Object {
3107
0,
3108
1,
3109
3,
2976
- 5,
3110
2,
2978
- 0,
3111
2,
3112
0,
3113
+ 1,
3114
+ 0,
3115
+ 4,
3116
+ 3,
3117
+ 0,
3118
+ 1,
3119
4,
3120
+ 5,
3121
3,
3122
0,
3123
+ 2,
3124
+ 0,
3125
+ 4,
3126
+ 4,
3127
+ 0,
3128
+ ],
3129
+ Array [
3130
+ 1,
3131
+ 1,
3132
+ 0,
3133
],
3134
Array [
3135
1,
packages/react-devtools-shared/src/__tests__/profilingCache-test.js
+26
-8
@@ -311,6 +311,7 @@ describe('ProfilingCache', () => {
311
312
it('should properly detect changed hooks', () => {
313
const Context = React.createContext(0);
314
+ const Context2 = React.createContext(0);
315
316
function reducer(state, action) {
317
switch (action.type) {
@@ -332,6 +333,7 @@ describe('ProfilingCache', () => {
333
// This hook's return value may change between renders,
334
// but the hook itself isn't stateful.
335
React.useContext(Context);
336
+ React.useContext(Context2);
337
338
// These hooks and their dependencies may not change between renders.
339
// We're using them to ensure that they don't trigger false positives.
@@ -351,7 +353,9 @@ describe('ProfilingCache', () => {
353
utils.act(() =>
354
legacyRender(
355
<Context.Provider value={true}>
354
- <Component count={1} />
356
+ <Context2.Provider value={true}>
357
+ <Component count={1} />
358
+ </Context2.Provider>
359
</Context.Provider>,
360
container,
361
),
@@ -361,7 +365,9 @@ describe('ProfilingCache', () => {
365
utils.act(() =>
366
legacyRender(
367
<Context.Provider value={true}>
364
- <Component count={2} />
368
+ <Context2.Provider value={true}>
369
+ <Component count={2} />
370
+ </Context2.Provider>
371
</Context.Provider>,
372
container,
373
),
@@ -373,17 +379,29 @@ describe('ProfilingCache', () => {
379
// Fourth render has a changed state hook
380
utils.act(() => setState('def'));
381
376
- // Fifth render has a changed context value, but no changed hook.
377
- // Technically, DevTools will miss this "context" change since it only tracks legacy context.
382
+ // Fifth render has a changed context value for context 1, but no changed hook.
383
utils.act(() =>
384
legacyRender(
385
<Context.Provider value={false}>
381
- <Component count={2} />
386
+ <Context2.Provider value={true}>
387
+ <Component count={2} />
388
+ </Context2.Provider>
389
</Context.Provider>,
390
container,
391
),
392
);
393
394
+ // Sixth render has another changed context value for context 2, but no changed hook.
395
+ utils.act(() =>
396
+ legacyRender(
397
+ <Context.Provider value={false}>
398
+ <Context2.Provider value={false}>
399
+ <Component count={2} />
400
+ </Context2.Provider>
401
+ </Context.Provider>,
402
+ container,
403
+ ),
404
+ );
405
utils.act(() => store.profilerStore.stopProfiling());
406
407
const allCommitData = [];
@@ -403,7 +421,7 @@ describe('ProfilingCache', () => {
421
422
const rootID = store.roots[0];
423
406
- for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
424
+ for (let commitIndex = 0; commitIndex < 6; commitIndex++) {
425
utils.act(() => {
426
TestRenderer.create(
427
<Validator
@@ -415,12 +433,12 @@ describe('ProfilingCache', () => {
433
});
434
}
435
418
- expect(allCommitData).toHaveLength(5);
436
+ expect(allCommitData).toHaveLength(6);
437
438
// Export and re-import profile data and make sure it is retained.
439
utils.exportImportHelper(bridge, store);
440
423
- for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
441
+ for (let commitIndex = 0; commitIndex < 6; commitIndex++) {
442
utils.act(() => {
443
TestRenderer.create(
444
<Validator
packages/react-devtools-shared/src/backend/renderer.js
+54
-22
@@ -960,6 +960,7 @@ export function attach(
960
961
return false;
962
}
963
+
964
// NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods
965
function getElementTypeForFiber(fiber: Fiber): ElementType {
966
const {type, tag} = fiber;
@@ -1236,6 +1237,7 @@ export function attach(
1237
1238
function updateContextsForFiber(fiber: Fiber) {
1239
switch (getElementTypeForFiber(fiber)) {
1240
+ case ElementTypeFunction:
1241
case ElementTypeClass:
1242
if (idToContextsMap !== null) {
1243
const id = getFiberIDThrows(fiber);
@@ -1254,11 +1256,12 @@ export function attach(
1256
const NO_CONTEXT = {};
1257
1258
function getContextsForFiber(fiber: Fiber): [Object, any] | null {
1259
+ let legacyContext = NO_CONTEXT;
1260
+ let modernContext = NO_CONTEXT;
1261
+
1262
switch (getElementTypeForFiber(fiber)) {
1263
case ElementTypeClass:
1264
const instance = fiber.stateNode;
1260
- let legacyContext = NO_CONTEXT;
1261
- let modernContext = NO_CONTEXT;
1265
if (instance != null) {
1266
if (
1267
instance.constructor &&
@@ -1272,6 +1275,13 @@ export function attach(
1275
}
1276
}
1277
}
1278
+ return [legacyContext, modernContext];
1279
+ case ElementTypeFunction:
1280
+ const dependencies = fiber.dependencies;
1281
+ if (dependencies && dependencies.firstContext) {
1282
+ modernContext = dependencies.firstContext;
1283
+ }
1284
+
1285
return [legacyContext, modernContext];
1286
default:
1287
return null;
@@ -1291,31 +1301,50 @@ export function attach(
1301
}
1302
1303
function getContextChangedKeys(fiber: Fiber): null | boolean | Array<string> {
1294
- switch (getElementTypeForFiber(fiber)) {
1295
- case ElementTypeClass:
1296
- if (idToContextsMap !== null) {
1297
- const id = getFiberIDThrows(fiber);
1298
- const prevContexts = idToContextsMap.has(id)
1299
- ? idToContextsMap.get(id)
1300
- : null;
1301
- const nextContexts = getContextsForFiber(fiber);
1304
+ if (idToContextsMap !== null) {
1305
+ const id = getFiberIDThrows(fiber);
1306
+ const prevContexts = idToContextsMap.has(id)
1307
+ ? idToContextsMap.get(id)
1308
+ : null;
1309
+ const nextContexts = getContextsForFiber(fiber);
1310
1303
- if (prevContexts == null || nextContexts == null) {
1304
- return null;
1311
+ if (prevContexts == null || nextContexts == null) {
1312
+ return null;
1313
+ }
1314
+
1315
+ const [prevLegacyContext, prevModernContext] = prevContexts;
1316
+ const [nextLegacyContext, nextModernContext] = nextContexts;
1317
+
1318
+ switch (getElementTypeForFiber(fiber)) {
1319
+ case ElementTypeClass:
1320
+ if (prevContexts && nextContexts) {
1321
+ if (nextLegacyContext !== NO_CONTEXT) {
1322
+ return getChangedKeys(prevLegacyContext, nextLegacyContext);
1323
+ } else if (nextModernContext !== NO_CONTEXT) {
1324
+ return prevModernContext !== nextModernContext;
1325
+ }
1326
}
1327
+ break;
1328
+ case ElementTypeFunction:
1329
+ if (nextModernContext !== NO_CONTEXT) {
1330
+ let prevContext = prevModernContext;
1331
+ let nextContext = nextModernContext;
1332
+
1333
+ while (prevContext && nextContext) {
1334
+ if (!is(prevContext.memoizedValue, nextContext.memoizedValue)) {
1335
+ return true;
1336
+ }
1337
1307
- const [prevLegacyContext, prevModernContext] = prevContexts;
1308
- const [nextLegacyContext, nextModernContext] = nextContexts;
1338
+ prevContext = prevContext.next;
1339
+ nextContext = nextContext.next;
1340
+ }
1341
1310
- if (nextLegacyContext !== NO_CONTEXT) {
1311
- return getChangedKeys(prevLegacyContext, nextLegacyContext);
1312
- } else if (nextModernContext !== NO_CONTEXT) {
1313
- return prevModernContext !== nextModernContext;
1342
+ return false;
1343
}
1315
- }
1316
- break;
1317
- default:
1318
- break;
1344
+ break;
1345
+ default:
1346
+ break;
1347
+ }
1348
}
1349
return null;
1350
}
@@ -2859,6 +2888,7 @@ export function attach(
2888
// Otherwise B has to be current branch.
2889
return alternate;
2890
}
2891
+
2892
// END copied code
2893
2894
function prepareViewAttributeSource(
@@ -3860,6 +3890,7 @@ export function attach(
3890
// Map of id and its force error status: true (error), false (toggled off),
3891
// null (do nothing)
3892
const forceErrorForFiberIDs = new Map();
3893
+
3894
function shouldErrorFiberAccordingToMap(fiber) {
3895
if (typeof setErrorHandler !== 'function') {
3896
throw new Error(
@@ -3924,6 +3955,7 @@ export function attach(
3955
}
3956
3957
const forceFallbackForSuspenseIDs = new Set();
3958
+
3959
function shouldSuspendFiberAccordingToSet(fiber) {
3960
const maybeID = getFiberIDUnsafe(((fiber: any): Fiber));
3961
return maybeID !== null && forceFallbackForSuspenseIDs.has(maybeID);
packages/react-devtools-shell/src/app/InspectableElements/Contexts.js
+136
-1
@@ -8,7 +8,7 @@
8
*/
9
10
import * as React from 'react';
11
-import {createContext, Component, useContext} from 'react';
11
+import {createContext, Component, useContext, useState} from 'react';
12
import PropTypes from 'prop-types';
13
14
function someNamedFunction() {}
@@ -73,6 +73,43 @@ class LegacyContextConsumer extends Component<any> {
73
}
74
}
75
76
+class LegacyContextProviderWithUpdates extends Component<any> {
77
+ constructor(props) {
78
+ super(props);
79
+ this.state = {type: 'desktop'};
80
+ }
81
+
82
+ getChildContext() {
83
+ return {type: this.state.type};
84
+ }
85
+
86
+ handleChange = event => {
87
+ this.setState({type: event.target.value});
88
+ };
89
+
90
+ render() {
91
+ return (
92
+ <>
93
+ <LegacyFunctionalContextConsumer />
94
+ <div>
95
+ <input value={this.state.type} onChange={this.handleChange} />
96
+ </div>
97
+ </>
98
+ );
99
+ }
100
+}
101
+
102
+LegacyContextProviderWithUpdates.childContextTypes = {
103
+ type: PropTypes.string,
104
+};
105
+
106
+function LegacyFunctionalContextConsumer(props, context) {
107
+ return formatContextForDisplay('LegacyFunctionContextConsumer', context.type);
108
+}
109
+LegacyFunctionalContextConsumer.contextTypes = {
110
+ type: PropTypes.string,
111
+};
112
+
113
const ModernContext = createContext();
114
ModernContext.displayName = 'ModernContext';
115
const ArrayContext = createContext(contextData.array);
@@ -105,6 +142,101 @@ function FunctionalContextConsumer() {
142
return formatContextForDisplay('FunctionalContextConsumer', value);
143
}
144
145
+const StringContextWithUpdates = createContext({
146
+ string: contextData.string,
147
+ setString: (string: string) => {},
148
+});
149
+const StringContextWithUpdates2 = createContext({
150
+ string2: contextData.string,
151
+ setString2: (string: string) => {},
152
+});
153
+
154
+function FunctionalContextProviderWithContextUpdates() {
155
+ const [string, setString] = useState(contextData.string);
156
+ const [string2, setString2] = useState(contextData.string);
157
+ const value = {string, setString};
158
+ const value2 = {string2, setString2};
159
+
160
+ return (
161
+ <StringContextWithUpdates.Provider value={value}>
162
+ <StringContextWithUpdates2.Provider value={value2}>
163
+ <FunctionalContextConsumerWithContextUpdates />
164
+ </StringContextWithUpdates2.Provider>
165
+ </StringContextWithUpdates.Provider>
166
+ );
167
+}
168
+
169
+function FunctionalContextConsumerWithContextUpdates() {
170
+ const {string, setString} = useContext(StringContextWithUpdates);
171
+ const {string2, setString2} = useContext(StringContextWithUpdates2);
172
+ const [state, setState] = useState('state');
173
+
174
+ const handleChange = e => setString(e.target.value);
175
+ const handleChange2 = e => setString2(e.target.value);
176
+
177
+ return (
178
+ <>
179
+ {formatContextForDisplay(
180
+ 'FunctionalContextConsumerWithUpdates',
181
+ `context: ${string}, context 2: ${string2}`,
182
+ )}
183
+ <div>
184
+ context: <input value={string} onChange={handleChange} />
185
+ </div>
186
+ <div>
187
+ context 2: <input value={string2} onChange={handleChange2} />
188
+ </div>
189
+ <div>
190
+ {state}
191
+ <div>
192
+ test state:{' '}
193
+ <input value={state} onChange={e => setState(e.target.value)} />
194
+ </div>
195
+ </div>
196
+ </>
197
+ );
198
+}
199
+
200
+class ModernClassContextProviderWithUpdates extends Component<any> {
201
+ constructor(props) {
202
+ super(props);
203
+ this.setString = string => {
204
+ this.setState({string});
205
+ };
206
+
207
+ this.state = {
208
+ string: contextData.string,
209
+ setString: this.setString,
210
+ };
211
+ }
212
+
213
+ render() {
214
+ return (
215
+ <StringContextWithUpdates.Provider value={this.state}>
216
+ <ModernClassContextConsumerWithUpdates />
217
+ </StringContextWithUpdates.Provider>
218
+ );
219
+ }
220
+}
221
+
222
+class ModernClassContextConsumerWithUpdates extends Component<any> {
223
+ render() {
224
+ return (
225
+ <StringContextWithUpdates.Consumer>
226
+ {({string, setString}) => (
227
+ <>
228
+ {formatContextForDisplay(
229
+ 'ModernClassContextConsumerWithUpdates',
230
+ string,
231
+ )}
232
+ <input value={string} onChange={e => setString(e.target.value)} />
233
+ </>
234
+ )}
235
+ </StringContextWithUpdates.Consumer>
236
+ );
237
+ }
238
+}
239
+
240
export default function Contexts() {
241
return (
242
<div>
@@ -113,6 +245,7 @@ export default function Contexts() {
245
<LegacyContextProvider>
246
<LegacyContextConsumer />
247
</LegacyContextProvider>
248
+ <LegacyContextProviderWithUpdates />
249
<ModernContext.Provider value={contextData}>
250
<ModernContext.Consumer>
251
{value => formatContextForDisplay('ModernContext.Consumer', value)}
@@ -120,6 +253,8 @@ export default function Contexts() {
253
<ModernContextType />
254
</ModernContext.Provider>
255
<FunctionalContextConsumer />
256
+ <FunctionalContextProviderWithContextUpdates />
257
+ <ModernClassContextProviderWithUpdates />
258
<ArrayContext.Consumer>
259
{value => formatContextForDisplay('ArrayContext.Consumer', value)}
260
</ArrayContext.Consumer>