@@ -316,145 +316,130 @@ async function singleFetchLoaderNavigationStrategy(
316316 matches : DataStrategyFunctionArgs [ "matches" ] ,
317317 basename : string | undefined
318318) {
319- // Track which routes need a server load - in case we need to tack on a
320- // `_routes` param
321- let routesParams = new Set < string > ( ) ;
319+ // Routes that need to be loaded from the server
320+ let serverLoaderRouteDfds = new Map < string , Deferred > ( ) ;
322321
323- // We only add `_routes` when one or more routes opts out of a load via
324- // `shouldRevalidate` or `clientLoader`
322+ // Did any routes opt-out of a server loader via`shouldRevalidate`/`clientLoader`?
325323 let foundOptOutRoute = false ;
326324
327- // Deferreds for each route so we can be sure they've all loaded via
328- // `match.resolve()`, and a singular promise that can tell us all routes
329- // have been resolved
325+ // Deferreds to ensure all routes have loaded via match.resolve()
330326 let routeDfds = matches . map ( ( ) => createDeferred < void > ( ) ) ;
331- let routesLoadedPromise = Promise . all ( routeDfds . map ( ( d ) => d . promise ) ) ;
332327
333- // Deferred that we'll use for the call to the server that each match can
334- // await and parse out it's specific result
335- let singleFetchDfd = createDeferred < SingleFetchResults > ( ) ;
328+ // We'll build up this results object as we loop through matches
329+ let results : Record < string , DataStrategyResult > = { } ;
336330
337- // Base URL and RequestInit for calls to the server
331+ // Base URL/ RequestInit for calls to the server
338332 let url = stripIndexParam ( singleFetchUrl ( request . url , basename ) ) ;
339333 let init = await createRequestInit ( request ) ;
340334
341- // We'll build up this results object as we loop through matches
342- let results : Record < string , DataStrategyResult > = { } ;
343-
344335 let resolvePromise = Promise . all (
345336 matches . map ( async ( m , i ) =>
346337 m . resolve ( async ( handler ) => {
347338 routeDfds [ i ] . resolve ( ) ;
348339
349- let manifestRoute = manifest . routes [ m . route . id ] ;
350-
351- // Note: If this logic changes for routes that should not participate
352- // in Single Fetch, make sure you update getLowestLoadingIndex above
353- // as well
354- if ( ! m . shouldLoad ) {
355- // If we're not yet initialized and this is the initial load, respect
356- // `shouldLoad` because we're only dealing with `clientLoader.hydrate`
357- // routes which will fall into the `clientLoader` section below.
358- if ( ! router . state . initialized ) {
359- return ;
360- }
340+ let { id : routeId , shouldRevalidate } = m . route ;
341+ let manifestRoute = manifest . routes [ routeId ] ;
342+ invariant ( manifestRoute , "No manifest route found for dataStrategy" ) ;
343+ let { hasLoader, hasClientLoader } = manifestRoute ;
344+
345+ // Short circuit if there's no loader to call. Give this route an
346+ // `undefined` result because we always have a `loader` on the client
347+ // route in framework mode (for loading module/styles) so this ensures
348+ // it doesn't think it's waiting for data and trigger `HydrateFallback`
349+ if ( ! hasLoader && ! hasClientLoader ) {
350+ results [ routeId ] = { type : "data" , result : undefined } ;
351+ return ;
352+ }
361353
362- // Otherwise, we opt out if we currently have data and a
363- // `shouldRevalidate` function. This implies that the user opted out
364- // via `shouldRevalidate`
365- if (
366- m . route . id in router . state . loaderData &&
367- manifestRoute &&
368- m . route . shouldRevalidate
369- ) {
370- if ( manifestRoute . hasLoader ) {
371- // If we have a server loader, make sure we don't include it in the
372- // single fetch .data request
373- foundOptOutRoute = true ;
374- }
375- return ;
354+ // Respect `shouldLoad` on initial load because we're only dealing with
355+ // `clientLoader.hydrate` routes which will fall into the `clientLoader`
356+ // section below.
357+ if ( ! m . shouldLoad && ! router . state . initialized ) {
358+ return ;
359+ }
360+
361+ // Otherwise, skip the call if we currently have data and a `shouldRevalidate`
362+ // function - which implies that the user opted out via `shouldRevalidate`
363+ if (
364+ ! m . shouldLoad &&
365+ routeId in router . state . loaderData &&
366+ shouldRevalidate
367+ ) {
368+ // If a server loader exists, ensure it's excluded from the .data request
369+ if ( hasLoader ) {
370+ foundOptOutRoute = true ;
376371 }
372+ return ;
377373 }
378374
379375 // When a route has a client loader, it opts out of the singular call and
380376 // calls it's server loader via `serverLoader()` using a `?_routes` param
381- if ( manifestRoute && manifestRoute . hasClientLoader ) {
382- if ( manifestRoute . hasLoader ) {
377+ if ( hasClientLoader ) {
378+ if ( hasLoader ) {
383379 foundOptOutRoute = true ;
384380 }
385381 try {
386- let result = await fetchSingleLoader (
387- handler ,
388- url ,
389- init ,
390- m . route . id
391- ) ;
392- results [ m . route . id ] = { type : "data" , result } ;
382+ let result = await fetchSingleLoader ( handler , url , init , routeId ) ;
383+ results [ routeId ] = { type : "data" , result } ;
393384 } catch ( e ) {
394- results [ m . route . id ] = { type : "error" , result : e } ;
385+ results [ routeId ] = { type : "error" , result : e } ;
395386 }
396387 return ;
397388 }
398389
399- // Load this route on the server if it has a loader
400- if ( manifestRoute && manifestRoute . hasLoader ) {
401- routesParams . add ( m . route . id ) ;
402- }
403-
404- // Lump this match in with the others on a singular promise
405- try {
406- let result = await handler ( async ( ) => {
407- let data = await singleFetchDfd . promise ;
408- return unwrapSingleFetchResults ( data , m . route . id ) ;
409- } ) ;
410- results [ m . route . id ] = {
411- type : "data" ,
412- result,
413- } ;
414- } catch ( e ) {
415- results [ m . route . id ] = {
416- type : "error" ,
417- result : e ,
418- } ;
419- }
390+ let dfd = createDeferred ( ) ;
391+ serverLoaderRouteDfds . set ( routeId , dfd ) ;
392+ await handler ( ( ) => dfd . promise ) ;
420393 } )
421394 )
422395 ) ;
423396
424397 // Wait for all routes to resolve above before we make the HTTP call
425- await routesLoadedPromise ;
398+ await Promise . all ( routeDfds . map ( ( d ) => d . promise ) ) ;
426399
427- // We can skip the server call:
428- // - On initial hydration - only clientLoaders can pass through via `clientLoader.hydrate`
429- // - If there are no routes to fetch from the server
400+ // Only make the server call if:
401+ // - We have loaders on the server that need fetching
402+ // - We're already hydrated - during initial hydration only clientLoaders can
403+ // run (via `clientLoader.hydrate`) which are handled above
430404 //
431405 // One exception - if we are performing an HDR revalidation we have to call
432406 // the server in case a new loader has shown up that the manifest doesn't yet
433407 // know about
434408 if (
435- ( ! router . state . initialized || routesParams . size === 0 ) &&
436- ! window . __reactRouterHdrActive
409+ ( serverLoaderRouteDfds . size > 0 && router . state . initialized ) ||
410+ window . __reactRouterHdrActive
437411 ) {
438- singleFetchDfd . resolve ( { } ) ;
439- } else {
412+ // When routes have opted out, add a `_routes` param to filter server loaders
413+ // Skipped in `ssr:false` because we expect to be loading static `.data` files
414+ if ( ssr && foundOptOutRoute && serverLoaderRouteDfds . size > 0 ) {
415+ let routes = [ ...serverLoaderRouteDfds . keys ( ) ] . join ( "," ) ;
416+ url . searchParams . set ( "_routes" , routes ) ;
417+ }
418+
440419 try {
441- // When one or more routes have opted out, we add a _routes param to
442- // limit the loaders to those that have a server loader and did not
443- // opt out
444- if ( ssr && foundOptOutRoute && routesParams . size > 0 ) {
445- url . searchParams . set (
446- "_routes" ,
447- matches
448- . filter ( ( m ) => routesParams . has ( m . route . id ) )
449- . map ( ( m ) => m . route . id )
450- . join ( "," )
451- ) ;
420+ let { data } = ( await fetchAndDecode ( url , init ) ) as {
421+ data : SingleFetchResults ;
422+ } ;
423+ for ( let [ routeId , dfd ] of serverLoaderRouteDfds ) {
424+ try {
425+ results [ routeId ] = {
426+ type : "data" ,
427+ result : unwrapSingleFetchResults ( data , routeId ) ,
428+ } ;
429+ dfd . resolve ( results [ routeId ] ) ;
430+ } catch ( e ) {
431+ results [ routeId ] = { type : "error" , result : e } ;
432+ dfd . reject ( results [ routeId ] ) ;
433+ }
452434 }
453-
454- let data = await fetchAndDecode ( url , init ) ;
455- singleFetchDfd . resolve ( data . data as SingleFetchResults ) ;
456435 } catch ( e ) {
457- singleFetchDfd . reject ( e as Error ) ;
436+ serverLoaderRouteDfds . forEach ( ( dfd , routeId ) => {
437+ results [ routeId ] = {
438+ type : "error" ,
439+ result : e ,
440+ } ;
441+ dfd . reject ( results [ routeId ] ) ;
442+ } ) ;
458443 }
459444 }
460445
@@ -673,17 +658,18 @@ function unwrapSingleFetchResult(result: SingleFetchResult, routeId: string) {
673658 }
674659}
675660
661+ type Deferred = ReturnType < typeof createDeferred > ;
676662function createDeferred < T = unknown > ( ) {
677663 let resolve : ( val ?: any ) => Promise < void > ;
678- let reject : ( error ?: Error ) => Promise < void > ;
664+ let reject : ( error ?: unknown ) => Promise < void > ;
679665 let promise = new Promise < T > ( ( res , rej ) => {
680666 resolve = async ( val : T ) => {
681667 res ( val ) ;
682668 try {
683669 await promise ;
684670 } catch ( e ) { }
685671 } ;
686- reject = async ( error ?: Error ) => {
672+ reject = async ( error ?: unknown ) => {
687673 rej ( error ) ;
688674 try {
689675 await promise ;
0 commit comments