@@ -1385,4 +1385,177 @@ describe('dehydration and rehydration', () => {
13851385 // error and test will fail
13861386 await originalPromise
13871387 } )
1388+
1389+ // Companion to the test above: when the query already exists in the cache
1390+ // (e.g. after an initial render or a first hydration pass), the same
1391+ // synchronous thenable resolution must also produce status: 'success'.
1392+ // Previously the if (query) branch would spread status: 'pending' from the
1393+ // server state without correcting it for the resolved data.
1394+ it ( 'should set status to success when rehydrating an existing pending query with a synchronously resolved promise' , async ( ) => {
1395+ const key = queryKey ( )
1396+ // --- server ---
1397+
1398+ const serverQueryClient = new QueryClient ( {
1399+ defaultOptions : {
1400+ dehydrate : { shouldDehydrateQuery : ( ) => true } ,
1401+ } ,
1402+ } )
1403+
1404+ let resolvePrefetch : undefined | ( ( value ?: unknown ) => void )
1405+ const prefetchPromise = new Promise ( ( res ) => {
1406+ resolvePrefetch = res
1407+ } )
1408+ // Keep the query pending so it dehydrates with status: 'pending' and a promise
1409+ void serverQueryClient . prefetchQuery ( {
1410+ queryKey : key ,
1411+ queryFn : ( ) => prefetchPromise ,
1412+ } )
1413+
1414+ const dehydrated = dehydrate ( serverQueryClient )
1415+ expect ( dehydrated . queries [ 0 ] ?. state . status ) . toBe ( 'pending' )
1416+
1417+ // Simulate a synchronous thenable – models a React streaming promise that
1418+ // resolved before the second hydrate() call.
1419+ resolvePrefetch ?.( 'server data' )
1420+ // @ts -expect-error
1421+ dehydrated . queries [ 0 ] . promise . then = ( cb ) => {
1422+ cb ?.( 'server data' )
1423+ // @ts -expect-error
1424+ return dehydrated . queries [ 0 ] . promise
1425+ }
1426+
1427+ // --- client ---
1428+ // Query already exists in the cache in a pending state, as it would after
1429+ // a first hydration pass or an initial render.
1430+ const clientQueryClient = new QueryClient ( )
1431+ void clientQueryClient . prefetchQuery ( {
1432+ queryKey : key ,
1433+ queryFn : ( ) => {
1434+ throw new Error ( 'QueryFn on client should not be called' )
1435+ } ,
1436+ } )
1437+
1438+ const query = clientQueryClient . getQueryCache ( ) . find ( { queryKey : key } ) !
1439+ expect ( query . state . status ) . toBe ( 'pending' )
1440+
1441+ hydrate ( clientQueryClient , dehydrated )
1442+
1443+ expect ( clientQueryClient . getQueryData ( key ) ) . toBe ( 'server data' )
1444+ expect ( query . state . status ) . toBe ( 'success' )
1445+
1446+ clientQueryClient . clear ( )
1447+ serverQueryClient . clear ( )
1448+ } )
1449+
1450+ it ( 'should not transition to a fetching/pending state when hydrating an already resolved promise into a new query' , async ( ) => {
1451+ const key = queryKey ( )
1452+ // --- server ---
1453+ const serverQueryClient = new QueryClient ( {
1454+ defaultOptions : {
1455+ dehydrate : { shouldDehydrateQuery : ( ) => true } ,
1456+ } ,
1457+ } )
1458+
1459+ let resolvePrefetch : undefined | ( ( value ?: unknown ) => void )
1460+ const prefetchPromise = new Promise ( ( res ) => {
1461+ resolvePrefetch = res
1462+ } )
1463+ void serverQueryClient . prefetchQuery ( {
1464+ queryKey : key ,
1465+ queryFn : ( ) => prefetchPromise ,
1466+ } )
1467+ const dehydrated = dehydrate ( serverQueryClient )
1468+
1469+ // Simulate a synchronous thenable – the promise was already resolved
1470+ // before we hydrate on the client
1471+ resolvePrefetch ?.( 'server data' )
1472+ // @ts -expect-error
1473+ dehydrated . queries [ 0 ] . promise . then = ( cb ) => {
1474+ cb ?.( 'server data' )
1475+ // @ts -expect-error
1476+ return dehydrated . queries [ 0 ] . promise
1477+ }
1478+
1479+ // --- client ---
1480+ const clientQueryClient = new QueryClient ( )
1481+
1482+ const states : Array < { status : string ; fetchStatus : string } > = [ ]
1483+ const unsubscribe = clientQueryClient . getQueryCache ( ) . subscribe ( ( event ) => {
1484+ if ( event . type === 'updated' ) {
1485+ const { status, fetchStatus } = event . query . state
1486+ states . push ( { status, fetchStatus } )
1487+ }
1488+ } )
1489+
1490+ hydrate ( clientQueryClient , dehydrated )
1491+ await vi . advanceTimersByTimeAsync ( 0 )
1492+ unsubscribe ( )
1493+
1494+ expect ( states ) . not . toContainEqual (
1495+ expect . objectContaining ( { fetchStatus : 'fetching' } ) ,
1496+ )
1497+ expect ( states ) . not . toContainEqual (
1498+ expect . objectContaining ( { status : 'pending' } ) ,
1499+ )
1500+
1501+ clientQueryClient . clear ( )
1502+ serverQueryClient . clear ( )
1503+ } )
1504+
1505+ it ( 'should not transition to a fetching/pending state when hydrating an already resolved promise into an existing query' , async ( ) => {
1506+ const key = queryKey ( )
1507+ // --- server ---
1508+ const serverQueryClient = new QueryClient ( {
1509+ defaultOptions : {
1510+ dehydrate : { shouldDehydrateQuery : ( ) => true } ,
1511+ } ,
1512+ } )
1513+
1514+ let resolvePrefetch : undefined | ( ( value ?: unknown ) => void )
1515+ const prefetchPromise = new Promise ( ( res ) => {
1516+ resolvePrefetch = res
1517+ } )
1518+ void serverQueryClient . prefetchQuery ( {
1519+ queryKey : key ,
1520+ queryFn : ( ) => prefetchPromise ,
1521+ } )
1522+ const dehydrated = dehydrate ( serverQueryClient )
1523+
1524+ // Simulate a synchronous thenable – the promise was already resolved
1525+ // before we hydrate on the client
1526+ resolvePrefetch ?.( 'server data' )
1527+ // @ts -expect-error
1528+ dehydrated . queries [ 0 ] . promise . then = ( cb ) => {
1529+ cb ?.( 'server data' )
1530+ // @ts -expect-error
1531+ return dehydrated . queries [ 0 ] . promise
1532+ }
1533+
1534+ // --- client ---
1535+ // Pre-populate with old data (updatedAt: 0 ensures dehydratedAt is newer)
1536+ const clientQueryClient = new QueryClient ( )
1537+ clientQueryClient . setQueryData ( key , 'old data' , { updatedAt : 0 } )
1538+
1539+ const states : Array < { status : string ; fetchStatus : string } > = [ ]
1540+ const unsubscribe = clientQueryClient . getQueryCache ( ) . subscribe ( ( event ) => {
1541+ if ( event . type === 'updated' ) {
1542+ const { status, fetchStatus } = event . query . state
1543+ states . push ( { status, fetchStatus } )
1544+ }
1545+ } )
1546+
1547+ hydrate ( clientQueryClient , dehydrated )
1548+ await vi . advanceTimersByTimeAsync ( 0 )
1549+ unsubscribe ( )
1550+
1551+ expect ( states ) . not . toContainEqual (
1552+ expect . objectContaining ( { fetchStatus : 'fetching' } ) ,
1553+ )
1554+ expect ( states ) . not . toContainEqual (
1555+ expect . objectContaining ( { status : 'pending' } ) ,
1556+ )
1557+
1558+ clientQueryClient . clear ( )
1559+ serverQueryClient . clear ( )
1560+ } )
13881561} )
0 commit comments