1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and 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. import {
    
  12.   Fragment,
    
  13.   Suspense,
    
  14.   unstable_SuspenseList as SuspenseList,
    
  15.   useState,
    
  16. } from 'react';
    
  17. 
    
  18. function SuspenseTree(): React.Node {
    
  19.   return (
    
  20.     <Fragment>
    
  21.       <h1>Suspense</h1>
    
  22.       <h4>Primary to Fallback Cycle</h4>
    
  23.       <PrimaryFallbackTest initialSuspend={false} />
    
  24.       <h4>Fallback to Primary Cycle</h4>
    
  25.       <PrimaryFallbackTest initialSuspend={true} />
    
  26.       <NestedSuspenseTest />
    
  27.       <SuspenseListTest />
    
  28.       <EmptySuspense />
    
  29.     </Fragment>
    
  30.   );
    
  31. }
    
  32. 
    
  33. function EmptySuspense() {
    
  34.   return <Suspense />;
    
  35. }
    
  36. 
    
  37. // $FlowFixMe[missing-local-annot]
    
  38. function PrimaryFallbackTest({initialSuspend}) {
    
  39.   const [suspend, setSuspend] = useState(initialSuspend);
    
  40.   const fallbackStep = useTestSequence('fallback', Fallback1, Fallback2);
    
  41.   const primaryStep = useTestSequence('primary', Primary1, Primary2);
    
  42.   return (
    
  43.     <Fragment>
    
  44.       <label>
    
  45.         <input
    
  46.           checked={suspend}
    
  47.           onChange={e => setSuspend(e.target.checked)}
    
  48.           type="checkbox"
    
  49.         />
    
  50.         Suspend
    
  51.       </label>
    
  52.       <br />
    
  53.       <Suspense fallback={fallbackStep}>
    
  54.         {suspend ? <Never /> : primaryStep}
    
  55.       </Suspense>
    
  56.     </Fragment>
    
  57.   );
    
  58. }
    
  59. 
    
  60. function useTestSequence(label: string, T1: any => any, T2: any => any) {
    
  61.   const [step, setStep] = useState(0);
    
  62.   const next: $FlowFixMe = (
    
  63.     <button onClick={() => setStep(s => (s + 1) % allSteps.length)}>
    
  64.       next {label} content
    
  65.     </button>
    
  66.   );
    
  67.   const allSteps: $FlowFixMe = [
    
  68.     <Fragment>{next}</Fragment>,
    
  69.     <Fragment>
    
  70.       {next} <T1 prop={step}>mount</T1>
    
  71.     </Fragment>,
    
  72.     <Fragment>
    
  73.       {next} <T1 prop={step}>update</T1>
    
  74.     </Fragment>,
    
  75.     <Fragment>
    
  76.       {next} <T2 prop={step}>several</T2> <T1 prop={step}>different</T1>{' '}
    
  77.       <T2 prop={step}>children</T2>
    
  78.     </Fragment>,
    
  79.     <Fragment>
    
  80.       {next} <T2 prop={step}>goodbye</T2>
    
  81.     </Fragment>,
    
  82.   ];
    
  83.   return allSteps[step];
    
  84. }
    
  85. 
    
  86. function NestedSuspenseTest() {
    
  87.   return (
    
  88.     <Fragment>
    
  89.       <h3>Nested Suspense</h3>
    
  90.       <Suspense fallback={<Fallback1>Loading outer</Fallback1>}>
    
  91.         <Parent />
    
  92.       </Suspense>
    
  93.     </Fragment>
    
  94.   );
    
  95. }
    
  96. 
    
  97. function Parent() {
    
  98.   return (
    
  99.     <div>
    
  100.       <Suspense fallback={<Fallback1>Loading inner 1</Fallback1>}>
    
  101.         <Primary1>Hello</Primary1>
    
  102.       </Suspense>{' '}
    
  103.       <Suspense fallback={<Fallback2>Loading inner 2</Fallback2>}>
    
  104.         <Primary2>World</Primary2>
    
  105.       </Suspense>
    
  106.       <br />
    
  107.       <Suspense fallback={<Fallback1>This will never load</Fallback1>}>
    
  108.         <Never />
    
  109.       </Suspense>
    
  110.       <br />
    
  111.       <b>
    
  112.         <LoadLater />
    
  113.       </b>
    
  114.     </div>
    
  115.   );
    
  116. }
    
  117. 
    
  118. function SuspenseListTest() {
    
  119.   return (
    
  120.     <>
    
  121.       <h1>SuspenseList</h1>
    
  122.       <SuspenseList revealOrder="forwards" tail="collapsed">
    
  123.         <div>
    
  124.           <Suspense fallback={<Fallback1>Loading 1</Fallback1>}>
    
  125.             <Primary1>Hello</Primary1>
    
  126.           </Suspense>
    
  127.         </div>
    
  128.         <div>
    
  129.           <LoadLater />
    
  130.         </div>
    
  131.         <div>
    
  132.           <Suspense fallback={<Fallback2>Loading 2</Fallback2>}>
    
  133.             <Primary2>World</Primary2>
    
  134.           </Suspense>
    
  135.         </div>
    
  136.       </SuspenseList>
    
  137.     </>
    
  138.   );
    
  139. }
    
  140. 
    
  141. function LoadLater() {
    
  142.   const [loadChild, setLoadChild] = useState(false);
    
  143.   return (
    
  144.     <Suspense
    
  145.       fallback={
    
  146.         <Fallback1 onClick={() => setLoadChild(true)}>Click to load</Fallback1>
    
  147.       }>
    
  148.       {loadChild ? (
    
  149.         <Primary1 onClick={() => setLoadChild(false)}>
    
  150.           Loaded! Click to suspend again.
    
  151.         </Primary1>
    
  152.       ) : (
    
  153.         <Never />
    
  154.       )}
    
  155.     </Suspense>
    
  156.   );
    
  157. }
    
  158. 
    
  159. function Never() {
    
  160.   throw new Promise(resolve => {});
    
  161. }
    
  162. 
    
  163. function Fallback1({prop, ...rest}: any) {
    
  164.   return <span {...rest} />;
    
  165. }
    
  166. 
    
  167. function Fallback2({prop, ...rest}: any) {
    
  168.   return <span {...rest} />;
    
  169. }
    
  170. 
    
  171. function Primary1({prop, ...rest}: any) {
    
  172.   return <span {...rest} />;
    
  173. }
    
  174. 
    
  175. function Primary2({prop, ...rest}: any) {
    
  176.   return <span {...rest} />;
    
  177. }
    
  178. 
    
  179. export default SuspenseTree;