Remove render prop option from Suspense (#13880)
This was the original, lower-level API before we landed on `fallback` instead. (We might add a different lower-level API in the future, likely alongside a new API for catching errors).
Andrew Clark committed
Oct 18, 2018 at 15:48 UTC
b738ced47717d7bd1e9083a3fbe9ab1be3b6588c
2 files changed
+17
-71
packages/react-reconciler/src/ReactFiberBeginWork.js
+12
-16
@@ -990,16 +990,6 @@ function updateSuspenseComponent(
990
}
991
}
992
993
- // If the `children` prop is a function, treat it like a render prop.
994
- // TODO: Remove this, or put it behind a feature flag
995
- const children = nextProps.children;
996
- let nextChildren;
997
- if (typeof children === 'function') {
998
- nextChildren = children(nextDidTimeout);
999
- } else {
1000
- nextChildren = nextDidTimeout ? nextProps.fallback : children;
1001
- }
1002
-
993
// This next part is a bit confusing. If the children timeout, we switch to
994
// showing the fallback children in place of the "primary" children.
995
// However, we don't want to delete the primary children because then their
@@ -1036,6 +1026,7 @@ function updateSuspenseComponent(
1026
// no previous state that needs to be preserved.
1027
if (nextDidTimeout) {
1028
// Mount separate fragments for primary and fallback children.
1029
+ const nextFallbackChildren = nextProps.fallback;
1030
const primaryChildFragment = createFiberFromFragment(
1031
null,
1032
mode,
@@ -1043,7 +1034,7 @@ function updateSuspenseComponent(
1034
null,
1035
);
1036
const fallbackChildFragment = createFiberFromFragment(
1046
- nextChildren,
1037
+ nextFallbackChildren,
1038
mode,
1039
renderExpirationTime,
1040
null,
@@ -1056,10 +1047,11 @@ function updateSuspenseComponent(
1047
child.return = next.return = workInProgress;
1048
} else {
1049
// Mount the primary children without an intermediate fragment fiber.
1050
+ const nextPrimaryChildren = nextProps.children;
1051
child = next = mountChildFibers(
1052
workInProgress,
1053
null,
1062
- nextChildren,
1054
+ nextPrimaryChildren,
1055
renderExpirationTime,
1056
);
1057
}
@@ -1076,6 +1068,7 @@ function updateSuspenseComponent(
1068
if (nextDidTimeout) {
1069
// Still timed out. Reuse the current primary children by cloning
1070
// its fragment. We're going to skip over these entirely.
1071
+ const nextFallbackChildren = nextProps.fallback;
1072
const primaryChildFragment = createWorkInProgress(
1073
currentFallbackChildFragment,
1074
currentFallbackChildFragment.pendingProps,
@@ -1086,7 +1079,7 @@ function updateSuspenseComponent(
1079
// working on.
1080
const fallbackChildFragment = (primaryChildFragment.sibling = createWorkInProgress(
1081
currentFallbackChildFragment,
1089
- nextChildren,
1082
+ nextFallbackChildren,
1083
currentFallbackChildFragment.expirationTime,
1084
));
1085
fallbackChildFragment.effectTag |= Placement;
@@ -1098,12 +1091,13 @@ function updateSuspenseComponent(
1091
} else {
1092
// No longer suspended. Switch back to showing the primary children,
1093
// and remove the intermediate fragment fiber.
1094
+ const nextPrimaryChildren = nextProps.children;
1095
const currentPrimaryChild = currentPrimaryChildFragment.child;
1096
const currentFallbackChild = currentFallbackChildFragment.child;
1097
const primaryChild = reconcileChildFibers(
1098
workInProgress,
1099
currentPrimaryChild,
1106
- nextChildren,
1100
+ nextPrimaryChildren,
1101
renderExpirationTime,
1102
);
1103
// Delete the fallback children.
@@ -1123,6 +1117,7 @@ function updateSuspenseComponent(
1117
if (nextDidTimeout) {
1118
// Timed out. Wrap the children in a fragment fiber to keep them
1119
// separate from the fallback children.
1120
+ const nextFallbackChildren = nextProps.fallback;
1121
const primaryChildFragment = createFiberFromFragment(
1122
// It shouldn't matter what the pending props are because we aren't
1123
// going to render this fragment.
@@ -1136,7 +1131,7 @@ function updateSuspenseComponent(
1131
currentPrimaryChild.return = primaryChildFragment;
1132
// Create a fragment from the fallback children, too.
1133
const fallbackChildFragment = (primaryChildFragment.sibling = createFiberFromFragment(
1139
- nextChildren,
1134
+ nextFallbackChildren,
1135
mode,
1136
renderExpirationTime,
1137
null,
@@ -1150,10 +1145,11 @@ function updateSuspenseComponent(
1145
} else {
1146
// Still haven't timed out. Continue rendering the children, like we
1147
// normally do.
1148
+ const nextPrimaryChildren = nextProps.children;
1149
next = child = reconcileChildFibers(
1150
workInProgress,
1151
currentPrimaryChild,
1156
- nextChildren,
1152
+ nextPrimaryChildren,
1153
renderExpirationTime,
1154
);
1155
}
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+5
-55
@@ -670,11 +670,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
670
671
it('throws a helpful error when an update is suspends without a placeholder', () => {
672
expect(() => {
673
- ReactNoop.flushSync(() =>
674
- ReactNoop.render(
675
- <Suspense>{() => <AsyncText text="Async" />}</Suspense>,
676
- ),
677
- );
673
+ ReactNoop.flushSync(() => ReactNoop.render(<AsyncText text="Async" />));
674
}).toThrow('An update was suspended, but no placeholder UI was provided.');
675
});
676
@@ -772,47 +768,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
768
expect(ReactNoop.getChildren()).toEqual([span('C')]);
769
});
770
775
- it('can hide a tree to unblock its surroundings', async () => {
776
- function App() {
777
- return (
778
- <Suspense maxDuration={1000}>
779
- {didTimeout => (
780
- <Fragment>
781
- <div hidden={didTimeout}>
782
- <AsyncText text="Async" ms={3000} />
783
- </div>
784
- {didTimeout ? <Text text="Loading..." /> : null}
785
- </Fragment>
786
- )}
787
- </Suspense>
788
- );
789
- }
790
-
791
- ReactNoop.render(<App />);
792
- expect(ReactNoop.flush()).toEqual(['Suspend! [Async]', 'Loading...']);
793
- expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
794
-
795
- ReactNoop.expire(2000);
796
- await advanceTimers(2000);
797
- expect(ReactNoop.flush()).toEqual([]);
798
- expect(ReactNoop.getChildrenAsJSX()).toEqual(
799
- <React.Fragment>
800
- <div hidden={true} />
801
- <span prop="Loading..." />
802
- </React.Fragment>,
803
- );
804
-
805
- ReactNoop.expire(1000);
806
- await advanceTimers(1000);
807
-
808
- expect(ReactNoop.flush()).toEqual(['Promise resolved [Async]', 'Async']);
809
- expect(ReactNoop.getChildrenAsJSX()).toEqual(
810
- <div>
811
- <span prop="Async" />
812
- </div>,
813
- );
814
- });
815
-
771
it('flushes all expired updates in a single batch', async () => {
772
class Foo extends React.Component {
773
componentDidUpdate() {
@@ -869,16 +824,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
824
}
825
826
function Delay({ms}) {
827
+ // Once ms has elapsed, render null. This allows the rest of the
828
+ // tree to resume rendering.
829
return (
873
- <Suspense maxDuration={ms}>
874
- {didTimeout => {
875
- if (didTimeout) {
876
- // Once ms has elapsed, render null. This allows the rest of the
877
- // tree to resume rendering.
878
- return null;
879
- }
880
- return <Never />;
881
- }}
830
+ <Suspense fallback={null} maxDuration={ms}>
831
+ <Never />
832
</Suspense>
833
);
834
}