Updates from inside componentDidMount/Update should have Task priority. This is only observable in incremental mode.
Updates from inside componentDidMount/Update should have Task priority. This is only observable in incremental mode.
Andrew Clark committed
Nov 4, 2016 at 13:57 UTC
d9d4ad60c540c88efb43a5e53bc42ba93a04ec1c
5 files changed
+67
-382
scripts/fiber/tests-failing.txt
+3
@@ -72,6 +72,9 @@ src/renderers/art/__tests__/ReactART-test.js
72
* resolves refs before componentDidMount
73
* resolves refs before componentDidUpdate
74
75
+src/renderers/dom/__tests__/ReactDOMProduction-test.js
76
+* should throw with an error code in production
77
+
78
src/renderers/dom/shared/__tests__/CSSPropertyOperations-test.js
79
* should set style attribute when styles exist
80
* should warn when using hyphenated style names
scripts/fiber/tests-passing.txt
+1
-13
@@ -453,7 +453,6 @@ src/renderers/dom/__tests__/ReactDOMProduction-test.js
453
* should use prod React
454
* should handle a simple flow
455
* should call lifecycle methods
456
-* should throw with an error code in production
456
457
src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
458
* should render strings as children
@@ -846,18 +845,6 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
845
* splits deferred work on multiple roots
846
* works on deferred roots in the order they were scheduled
847
* handles interleaved deferred and animation work
849
-* performs animation work in animation callback
850
-* schedules deferred work in animation callback
851
-* schedules deferred work and performs animation work in animation callback
852
-* performs animation work and schedules deferred work in animation callback
853
-* performs deferred work in deferred callback if it has time
854
-* schedules deferred work in deferred callback if it runs out of time
855
-* performs animated work in deferred callback if it has time
856
-* performs animated work and deferred work in deferred callback if it has time
857
-* performs deferred and animated work work in deferred callback if it has time
858
-* schedules animated work in deferred callback if it runs out of time
859
-* schedules animated work and deferred work in deferred callback if it runs out of time
860
-* schedules deferred work and animated work in deferred callback if it runs out of time
848
849
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
850
* can update child nodes of a host instance
@@ -878,6 +865,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
865
* calls setState callback even if component bails out
866
* calls componentWillUnmount after a deletion, even if nested
867
* calls componentDidMount/Update after insertion/update
868
+* schedules sync updates when inside componentDidMount/Update
869
* invokes ref callbacks after insertion/update/unmount
870
* supports string refs
871
src/renderers/shared/fiber/ReactFiberScheduler.js
+3
@@ -209,7 +209,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
209
while (effectfulFiber) {
210
if (effectfulFiber.effectTag & (Update | Callback)) {
211
const current = effectfulFiber.alternate;
212
+ const previousPriorityContext = priorityContext;
213
+ priorityContext = TaskPriority;
214
commitLifeCycles(current, effectfulFiber);
215
+ priorityContext = previousPriorityContext;
216
}
217
const next = effectfulFiber.nextEffect;
218
// Ensure that we clean these up so that we don't accidentally keep them.
src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
-369
@@ -449,373 +449,4 @@ describe('ReactIncrementalScheduling', () => {
449
expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
450
expect(ReactNoop.getChildren('c')).toEqual([span('c:3')]);
451
});
452
-
453
- it('performs animation work in animation callback', () => {
454
- class Foo extends React.Component {
455
- componentDidMount() {
456
- // Animation work that will get performed during animation callback
457
- ReactNoop.performAnimationWork(() => {
458
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
459
- });
460
- }
461
- render() {
462
- return <span prop="a:1" />;
463
- }
464
- }
465
-
466
- // Schedule animation work
467
- ReactNoop.performAnimationWork(() => {
468
- ReactNoop.renderToRootWithID(<Foo />, 'a');
469
- });
470
-
471
- // Flushing animation work should flush animation work scheduled inside it
472
- ReactNoop.flushAnimationPri();
473
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
474
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
475
- });
476
-
477
- it('schedules deferred work in animation callback', () => {
478
- class Foo extends React.Component {
479
- componentDidMount() {
480
- // Deferred work that will get scheduled during animation callback
481
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
482
- }
483
- render() {
484
- return <span prop="a:1" />;
485
- }
486
- }
487
-
488
- // Schedule animation work
489
- ReactNoop.performAnimationWork(() => {
490
- ReactNoop.renderToRootWithID(<Foo />, 'a');
491
- });
492
-
493
- // Flushing animation work should not flush the deferred work
494
- ReactNoop.flushAnimationPri();
495
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
496
- expect(ReactNoop.getChildren('b')).toEqual([]);
497
-
498
- // Flush the deferred work
499
- ReactNoop.flushDeferredPri();
500
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
501
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
502
- });
503
-
504
- it('schedules deferred work and performs animation work in animation callback', () => {
505
- let hasScheduled = false;
506
- class Foo extends React.Component {
507
- componentDidMount() {
508
- // Deferred work that will get scheduled during animation callback
509
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
510
- // Animation work that will get performed during animation callback
511
- ReactNoop.performAnimationWork(() => {
512
- ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
513
- });
514
- // Deferred work that will get scheduled during animation callback
515
- ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
516
- hasScheduled = true;
517
- }
518
- render() {
519
- return <span prop="a:1" />;
520
- }
521
- }
522
-
523
- // Schedule animation work
524
- ReactNoop.performAnimationWork(() => {
525
- ReactNoop.renderToRootWithID(<Foo />, 'a');
526
- });
527
-
528
- // Flushing animation work should flush animation work scheduled inside it
529
- ReactNoop.flushAnimationPri();
530
- expect(hasScheduled).toBe(true);
531
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
532
- expect(ReactNoop.getChildren('b')).toEqual([]);
533
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
534
- expect(ReactNoop.getChildren('d')).toEqual([]);
535
-
536
- // Flush the deferred work
537
- ReactNoop.flushDeferredPri();
538
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
539
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
540
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
541
- expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
542
- });
543
-
544
- it('performs animation work and schedules deferred work in animation callback', () => {
545
- let hasScheduled = false;
546
- class Foo extends React.Component {
547
- componentDidMount() {
548
- // Animation work that will get performed during animation callback
549
- ReactNoop.performAnimationWork(() => {
550
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
551
- });
552
- // Deferred work that will get scheduled during animation callback
553
- ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
554
- // Animation work that will get performed during animation callback
555
- ReactNoop.performAnimationWork(() => {
556
- ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
557
- });
558
- hasScheduled = true;
559
- }
560
- render() {
561
- return <span prop="a:1" />;
562
- }
563
- }
564
-
565
- // Schedule animation work
566
- ReactNoop.performAnimationWork(() => {
567
- ReactNoop.renderToRootWithID(<Foo />, 'a');
568
- });
569
-
570
- // Flushing animation work should flush animation work scheduled inside it
571
- ReactNoop.flushAnimationPri();
572
- expect(hasScheduled).toBe(true);
573
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
574
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
575
- expect(ReactNoop.getChildren('c')).toEqual([]);
576
- expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
577
-
578
- // Flush the deferred work
579
- ReactNoop.flushDeferredPri();
580
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
581
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
582
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
583
- expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
584
- });
585
-
586
- it('performs deferred work in deferred callback if it has time', () => {
587
- class Foo extends React.Component {
588
- componentDidMount() {
589
- // Deferred work that will get performed during deferred callback
590
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
591
- }
592
- render() {
593
- return <span prop="a:1" />;
594
- }
595
- }
596
-
597
- // Schedule deferred work
598
- ReactNoop.renderToRootWithID(<Foo />, 'a');
599
-
600
- // Flushing deferred work should flush deferred work scheduled inside it
601
- ReactNoop.flushDeferredPri();
602
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
603
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
604
- });
605
-
606
- it('schedules deferred work in deferred callback if it runs out of time', () => {
607
- let hasScheduled = false;
608
- class Foo extends React.Component {
609
- componentDidMount() {
610
- // Deferred work that will get scheduled during deferred callback
611
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
612
- hasScheduled = true;
613
- }
614
- render() {
615
- return <span prop="a:1" />;
616
- }
617
- }
618
-
619
- // Schedule deferred work
620
- ReactNoop.renderToRootWithID(<Foo />, 'a');
621
-
622
- // Flush just enough deferred work to schedule more deferred work
623
- ReactNoop.flushDeferredPri(20);
624
- expect(hasScheduled).toBe(true);
625
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
626
- expect(ReactNoop.getChildren('b')).toEqual([]);
627
-
628
- // Flush the rest of the deferred work
629
- ReactNoop.flushDeferredPri(15);
630
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
631
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
632
- });
633
-
634
- it('performs animated work in deferred callback if it has time', () => {
635
- class Foo extends React.Component {
636
- componentDidMount() {
637
- // Animated work that will get performed during deferred callback
638
- ReactNoop.performAnimationWork(() => {
639
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
640
- });
641
- }
642
- render() {
643
- return <span prop="a:1" />;
644
- }
645
- }
646
-
647
- // Schedule deferred work
648
- ReactNoop.renderToRootWithID(<Foo />, 'a');
649
-
650
- // Flushing deferred work should flush animated work scheduled inside it
651
- ReactNoop.flushDeferredPri();
652
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
653
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
654
- });
655
-
656
- it('performs animated work and deferred work in deferred callback if it has time', () => {
657
- class Foo extends React.Component {
658
- componentDidMount() {
659
- // Deferred work that will get performed during deferred callback
660
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
661
- // Animation work that will get performed during deferred callback
662
- ReactNoop.performAnimationWork(() => {
663
- ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
664
- });
665
- // Deferred work that will get performed during deferred callback
666
- ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
667
- }
668
- render() {
669
- return <span prop="a:1" />;
670
- }
671
- }
672
-
673
- // Schedule deferred work
674
- ReactNoop.renderToRootWithID(<Foo />, 'a');
675
-
676
- // Flushing deferred work should flush both deferred and animated work scheduled inside it
677
- ReactNoop.flushDeferredPri();
678
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
679
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
680
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
681
- expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
682
- });
683
-
684
- it('performs deferred and animated work work in deferred callback if it has time', () => {
685
- class Foo extends React.Component {
686
- componentDidMount() {
687
- // Animation work that will get performed during deferred callback
688
- ReactNoop.performAnimationWork(() => {
689
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
690
- });
691
- // Deferred work that will get performed during deferred callback
692
- ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
693
- // Animation work that will get performed during deferred callback
694
- ReactNoop.performAnimationWork(() => {
695
- ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
696
- });
697
- }
698
- render() {
699
- return <span prop="a:1" />;
700
- }
701
- }
702
-
703
- // Schedule deferred work
704
- ReactNoop.renderToRootWithID(<Foo />, 'a');
705
-
706
- // Flushing deferred work should flush both deferred and animated work scheduled inside it
707
- ReactNoop.flushDeferredPri();
708
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
709
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
710
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
711
- expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
712
- });
713
-
714
- it('schedules animated work in deferred callback if it runs out of time', () => {
715
- let hasScheduled = false;
716
- class Foo extends React.Component {
717
- componentDidMount() {
718
- // Animated work that will get scheduled during deferred callback
719
- ReactNoop.performAnimationWork(() => {
720
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
721
- });
722
- hasScheduled = true;
723
- }
724
- render() {
725
- return <span prop="a:1" />;
726
- }
727
- }
728
-
729
- // Schedule deferred work
730
- ReactNoop.renderToRootWithID(<Foo />, 'a');
731
-
732
- // Flush just enough deferred work to schedule animated work
733
- ReactNoop.flushDeferredPri(20);
734
- expect(hasScheduled).toBe(true);
735
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
736
- expect(ReactNoop.getChildren('b')).toEqual([]);
737
-
738
- // Flush the rest of work in an animated callback.
739
- ReactNoop.flushAnimationPri();
740
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
741
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
742
- });
743
-
744
- it('schedules animated work and deferred work in deferred callback if it runs out of time', () => {
745
- let isScheduled = false;
746
- class Foo extends React.Component {
747
- componentDidMount() {
748
- // Deferred work that will get performed during deferred callback
749
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
750
- // Animation work that will get performed during deferred callback
751
- ReactNoop.performAnimationWork(() => {
752
- ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
753
- });
754
- // Deferred work that will get performed during deferred callback
755
- ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
756
- isScheduled = true;
757
- }
758
- render() {
759
- return <span prop="a:1" />;
760
- }
761
- }
762
-
763
- // Schedule deferred work
764
- ReactNoop.renderToRootWithID(<Foo />, 'a');
765
-
766
- // Flushing deferred work should schedule both deferred and animated work
767
- ReactNoop.flushDeferredPri(20);
768
- expect(isScheduled).toBe(true);
769
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
770
- expect(ReactNoop.getChildren('b')).toEqual([]);
771
- expect(ReactNoop.getChildren('c')).toEqual([]);
772
- expect(ReactNoop.getChildren('d')).toEqual([]);
773
-
774
- // Flush the rest of the work
775
- ReactNoop.flushDeferredPri();
776
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
777
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
778
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
779
- expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
780
- });
781
-
782
- it('schedules deferred work and animated work in deferred callback if it runs out of time', () => {
783
- let isScheduled = false;
784
- class Foo extends React.Component {
785
- componentDidMount() {
786
- // Animation work that will get performed during deferred callback
787
- ReactNoop.performAnimationWork(() => {
788
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
789
- });
790
- // Deferred work that will get performed during deferred callback
791
- ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
792
- // Animation work that will get performed during deferred callback
793
- ReactNoop.performAnimationWork(() => {
794
- ReactNoop.renderToRootWithID(<span prop="d:1" />, 'd');
795
- });
796
- isScheduled = true;
797
- }
798
- render() {
799
- return <span prop="a:1" />;
800
- }
801
- }
802
-
803
- // Schedule deferred work
804
- ReactNoop.renderToRootWithID(<Foo />, 'a');
805
-
806
- // Flushing deferred work should schedule both deferred and animated work
807
- ReactNoop.flushDeferredPri(20);
808
- expect(isScheduled).toBe(true);
809
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
810
- expect(ReactNoop.getChildren('b')).toEqual([]);
811
- expect(ReactNoop.getChildren('c')).toEqual([]);
812
- expect(ReactNoop.getChildren('d')).toEqual([]);
813
-
814
- // Flush the rest of the work
815
- ReactNoop.flushDeferredPri();
816
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
817
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
818
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
819
- expect(ReactNoop.getChildren('d')).toEqual([span('d:1')]);
820
- });
452
});
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
+60
@@ -1036,6 +1036,66 @@ describe('ReactIncrementalSideEffects', () => {
1036
1037
});
1038
1039
+ it('schedules sync updates when inside componentDidMount/Update', () => {
1040
+ var instance;
1041
+ var ops = [];
1042
+
1043
+ class Foo extends React.Component {
1044
+ state = { tick: 0 };
1045
+
1046
+ componentDidMount() {
1047
+ ops.push('componentDidMount (before setState): ' + this.state.tick);
1048
+ this.setState({ tick: 1 });
1049
+ // We're in a batch. Update hasn't flushed yet.
1050
+ ops.push('componentDidMount (after setState): ' + this.state.tick);
1051
+ }
1052
+
1053
+ componentDidUpdate() {
1054
+ ops.push('componentDidUpdate: ' + this.state.tick);
1055
+ if (this.state.tick === 2) {
1056
+ ops.push('componentDidUpdate (before setState): ' + this.state.tick);
1057
+ this.setState({ tick: 3 });
1058
+ ops.push('componentDidUpdate (after setState): ' + this.state.tick);
1059
+ // We're in a batch. Update hasn't flushed yet.
1060
+ }
1061
+ }
1062
+
1063
+ render() {
1064
+ ops.push('render: ' + this.state.tick);
1065
+ instance = this;
1066
+ return <span prop={this.state.tick} />;
1067
+ }
1068
+ }
1069
+
1070
+ ReactNoop.render(<Foo />);
1071
+
1072
+ ReactNoop.flushDeferredPri(20);
1073
+ expect(ops).toEqual([
1074
+ 'render: 0',
1075
+ 'componentDidMount (before setState): 0',
1076
+ 'componentDidMount (after setState): 0',
1077
+ // If the setState inside componentDidMount were deferred, there would be
1078
+ // no more ops. Because it has Task priority, we get these ops, too:
1079
+ 'render: 1',
1080
+ 'componentDidUpdate: 1',
1081
+ ]);
1082
+
1083
+ ops = [];
1084
+ instance.setState({ tick: 2 });
1085
+ ReactNoop.flushDeferredPri(20);
1086
+
1087
+ expect(ops).toEqual([
1088
+ 'render: 2',
1089
+ 'componentDidUpdate: 2',
1090
+ 'componentDidUpdate (before setState): 2',
1091
+ 'componentDidUpdate (after setState): 2',
1092
+ // If the setState inside componentDidUpdate were deferred, there would be
1093
+ // no more ops. Because it has Task priority, we get these ops, too:
1094
+ 'render: 3',
1095
+ 'componentDidUpdate: 3',
1096
+ ]);
1097
+ });
1098
+
1099
it('invokes ref callbacks after insertion/update/unmount', () => {
1100
1101
var classInstance = null;