CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-query

Hooks for managing, caching and syncing asynchronous and remote data in React

Pending
Overview
Eval results
Files

ssr.mddocs/

SSR & Hydration

Server-side rendering support with state dehydration and hydration for seamless SSR/SSG integration. React Query provides utilities to serialize server-side data and rehydrate it on the client.

Capabilities

useHydrate Hook

Hook for hydrating QueryClient with server-side rendered state.

/**
 * Hydrate QueryClient with server-side state
 * @param state - Dehydrated state from server
 * @param options - Hydration configuration options
 */
function useHydrate(
  state: unknown,
  options?: HydrateOptions & ContextOptions
): void;

interface HydrateOptions {
  /** Function to determine which queries should be hydrated */
  shouldDehydrateQuery?: ShouldDehydrateQueryFunction;
  /** Function to determine which mutations should be hydrated */
  shouldDehydrateMutation?: ShouldDehydrateMutationFunction;
  /** Default options for hydrated queries */
  defaultOptions?: {
    queries?: QueryOptions;
    mutations?: MutationOptions;
  };
}

interface ContextOptions {
  /** Custom React context to use */
  context?: React.Context<QueryClient | undefined>;
}

type ShouldDehydrateQueryFunction = (query: Query) => boolean;
type ShouldDehydrateMutationFunction = (mutation: Mutation) => boolean;

Usage Examples:

import { useHydrate } from "react-query";

// Basic hydration
function App({ dehydratedState }: { dehydratedState: any }) {
  useHydrate(dehydratedState);

  return (
    <div>
      <UserProfile />
      <PostsList />
    </div>
  );
}

// Hydration with options
function AppWithOptions({ dehydratedState }: { dehydratedState: any }) {
  useHydrate(dehydratedState, {
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000 // 1 minute
      }
    }
  });

  return <MainContent />;
}

// Conditional hydration
function ConditionalHydration({ dehydratedState, shouldHydrate }: { 
  dehydratedState: any; 
  shouldHydrate: boolean; 
}) {
  useHydrate(shouldHydrate ? dehydratedState : undefined);

  return <Content />;
}

Hydrate Component

Component wrapper for hydrating server-side state.

/**
 * Component that hydrates QueryClient with server-side state
 * @param props - Hydration props with state and options
 * @returns Children with hydrated state
 */
function Hydrate(props: HydrateProps): React.ReactElement;

interface HydrateProps {
  /** Dehydrated state from server */
  state?: unknown;
  /** Hydration configuration options */
  options?: HydrateOptions;
  /** Child components to render */
  children?: React.ReactNode;
}

Usage Examples:

import { Hydrate, QueryClient, QueryClientProvider } from "react-query";

// Basic component usage
function App({ pageProps }: { pageProps: any }) {
  const queryClient = new QueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <MyApp {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  );
}

// Next.js integration
function MyApp({ Component, pageProps }: AppProps) {
  const [queryClient] = useState(() => new QueryClient());

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  );
}

// Multiple hydration levels
function NestedHydration({ 
  globalState, 
  pageState, 
  children 
}: { 
  globalState: any; 
  pageState: any; 
  children: React.ReactNode; 
}) {
  return (
    <Hydrate state={globalState}>
      <div>
        <GlobalNav />
        <Hydrate state={pageState}>
          {children}
        </Hydrate>
      </div>
    </Hydrate>
  );
}

IsRestoringProvider Component

Context provider for tracking restoration state during SSR hydration.

/**
 * Provider for restoration state context
 * Used internally to track hydration state
 */
const IsRestoringProvider: React.Provider<boolean>;

/**
 * Hook to check if app is currently restoring from SSR
 * @returns Boolean indicating if restoration is in progress
 */
function useIsRestoring(): boolean;

Usage Examples:

import { useIsRestoring, IsRestoringProvider } from "react-query";

// Check restoration state
function MyComponent() {
  const isRestoring = useIsRestoring();

  if (isRestoring) {
    return <div>Restoring from server...</div>;
  }

  return <div>Client-side rendering active</div>;
}

// Custom restoration provider (rarely needed)
function CustomRestorationProvider({ children }: { children: React.ReactNode }) {
  const [isRestoring, setIsRestoring] = useState(true);

  useEffect(() => {
    // Custom logic to determine when restoration is complete
    const timer = setTimeout(() => setIsRestoring(false), 100);
    return () => clearTimeout(timer);
  }, []);

  return (
    <IsRestoringProvider value={isRestoring}>
      {children}
    </IsRestoringProvider>
  );
}

Core Hydration Functions

Low-level functions for dehydrating and hydrating QueryClient state.

/**
 * Serialize QueryClient state for server-side rendering
 * @param client - QueryClient instance to dehydrate
 * @param options - Dehydration configuration
 * @returns Serializable state object
 */
function dehydrate(
  client: QueryClient,
  options?: DehydrateOptions
): DehydratedState;

/**
 * Restore QueryClient state from serialized data
 * @param client - QueryClient instance to hydrate
 * @param dehydratedState - Serialized state from dehydrate
 * @param options - Hydration configuration
 */
function hydrate(
  client: QueryClient,
  dehydratedState: unknown,
  options?: HydrateOptions
): void;

interface DehydrateOptions {
  /** Function to determine which queries should be dehydrated */
  shouldDehydrateQuery?: ShouldDehydrateQueryFunction;
  /** Function to determine which mutations should be dehydrated */
  shouldDehydrateMutation?: ShouldDehydrateMutationFunction;
}

interface DehydratedState {
  /** Serialized queries */
  queries: Array<{
    queryKey: QueryKey;
    queryHash: string;
    state: QueryState;
  }>;
  /** Serialized mutations */
  mutations: Array<{
    mutationKey?: MutationKey;
    state: MutationState;
  }>;
}

Advanced Usage Patterns

Next.js Integration

Complete Next.js SSR/SSG setup with React Query:

// pages/_app.tsx
import { useState } from 'react';
import { Hydrate, QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
import type { AppProps } from 'next/app';

export default function MyApp({ Component, pageProps }: AppProps) {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000, // 1 minute
        refetchOnWindowFocus: false,
      },
    },
  }));

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

// pages/users/[id].tsx
import { GetServerSideProps } from 'next';
import { QueryClient, dehydrate } from 'react-query';

export default function UserPage({ userId }: { userId: string }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });

  const { data: posts } = useQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchUserPosts(userId)
  });

  return (
    <div>
      <h1>{user?.name}</h1>
      <PostsList posts={posts} />
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async ({ params }) => {
  const queryClient = new QueryClient();
  const userId = params?.id as string;

  // Prefetch data on server
  await queryClient.prefetchQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });

  await queryClient.prefetchQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchUserPosts(userId)
  });

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
      userId
    }
  };
};

Selective Hydration

Only hydrating specific queries to reduce bundle size:

// Server-side dehydration with filtering
export const getServerSideProps: GetServerSideProps = async () => {
  const queryClient = new QueryClient();

  // Prefetch multiple queries
  await Promise.all([
    queryClient.prefetchQuery({
      queryKey: ['user', 'current'],
      queryFn: fetchCurrentUser
    }),
    queryClient.prefetchQuery({
      queryKey: ['posts', 'popular'],
      queryFn: fetchPopularPosts
    }),
    queryClient.prefetchQuery({
      queryKey: ['analytics', 'daily'],
      queryFn: fetchDailyAnalytics
    })
  ]);

  return {
    props: {
      dehydratedState: dehydrate(queryClient, {
        shouldDehydrateQuery: (query) => {
          // Only dehydrate user and posts, not analytics
          const queryKey = query.queryKey[0] as string;
          return ['user', 'posts'].includes(queryKey);
        }
      })
    }
  };
};

// Client-side selective hydration
function App({ dehydratedState }: { dehydratedState: any }) {
  useHydrate(dehydratedState, {
    shouldDehydrateQuery: (query) => {
      // Only hydrate queries that are not stale
      return query.state.dataUpdatedAt > Date.now() - 60000; // 1 minute
    }
  });

  return <MainContent />;
}

Progressive Hydration

Hydrating data progressively as components mount:

function ProgressiveHydrationApp({ dehydratedState }: { dehydratedState: any }) {
  const [hydratedSections, setHydratedSections] = useState<string[]>([]);

  const hydrateSection = (section: string) => {
    if (!hydratedSections.includes(section)) {
      setHydratedSections(prev => [...prev, section]);
    }
  };

  return (
    <div>
      {/* Always hydrate critical data */}
      <Hydrate state={dehydratedState.critical}>
        <Header />
      </Hydrate>

      {/* Progressively hydrate sections */}
      <InView onChange={(inView) => inView && hydrateSection('main')}>
        {hydratedSections.includes('main') ? (
          <Hydrate state={dehydratedState.main}>
            <MainContent />
          </Hydrate>
        ) : (
          <div>Loading main content...</div>
        )}
      </InView>

      <InView onChange={(inView) => inView && hydrateSection('sidebar')}>
        {hydratedSections.includes('sidebar') ? (
          <Hydrate state={dehydratedState.sidebar}>
            <Sidebar />
          </Hydrate>
        ) : (
          <div>Loading sidebar...</div>
        )}
      </InView>
    </div>
  );
}

Error Handling in SSR

Handling errors during server-side prefetching:

// Robust server-side prefetching
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false, // Don't retry on server
        staleTime: Infinity, // Keep data fresh until client hydration
      },
    },
  });

  const userId = params?.id as string;

  try {
    // Prefetch critical data
    await queryClient.prefetchQuery({
      queryKey: ['user', userId],
      queryFn: () => fetchUser(userId)
    });

    // Prefetch optional data (don't fail if this errors)
    await queryClient.prefetchQuery({
      queryKey: ['posts', userId],
      queryFn: () => fetchUserPosts(userId)
    }).catch(error => {
      console.warn('Failed to prefetch posts:', error);
    });

  } catch (error) {
    console.error('Failed to prefetch user:', error);
    
    // Return error page or redirect
    return {
      notFound: true,
    };
  }

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
      userId
    }
  };
};

// Client-side error handling during hydration
function ErrorBoundaryWithHydration({ 
  children, 
  dehydratedState 
}: { 
  children: React.ReactNode; 
  dehydratedState: any; 
}) {
  const [hydrationError, setHydrationError] = useState<Error | null>(null);

  useEffect(() => {
    try {
      // Attempt hydration
      useHydrate(dehydratedState);
    } catch (error) {
      setHydrationError(error as Error);
    }
  }, [dehydratedState]);

  if (hydrationError) {
    return (
      <div>
        <h2>Hydration Error</h2>
        <p>Failed to restore server state. Falling back to client-side fetching.</p>
        <button onClick={() => setHydrationError(null)}>
          Try Again
        </button>
      </div>
    );
  }

  return <>{children}</>;
}

IsRestoringProvider and useIsRestoring

Provider and hook for tracking SSR hydration restoration state during client-side hydration.

/**
 * Provider component for managing restoration state during SSR hydration
 * @param value - Boolean indicating if restoration is in progress
 * @param children - Child components
 */
function IsRestoringProvider(props: {
  value: boolean;
  children: React.ReactNode;
}): React.ReactElement;

/**
 * Hook to check if queries are currently being restored during SSR hydration
 * @returns Boolean indicating if restoration is in progress
 */
function useIsRestoring(): boolean;

Usage Examples:

import { IsRestoringProvider, useIsRestoring } from "react-query";

// App-level restoration tracking
function App({ children }: { children: React.ReactNode }) {
  const [isRestoring, setIsRestoring] = useState(true);

  useEffect(() => {
    // Set restoration complete after hydration
    const timer = setTimeout(() => setIsRestoring(false), 100);
    return () => clearTimeout(timer);
  }, []);

  return (
    <IsRestoringProvider value={isRestoring}>
      <div className={isRestoring ? 'restoring' : 'restored'}>
        {children}
      </div>
    </IsRestoringProvider>
  );
}

// Component that adapts behavior during restoration
function DataComponent() {
  const isRestoring = useIsRestoring();
  const { data, isLoading } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData
  });

  // Don't show loading state during restoration to prevent flash
  if (isRestoring) {
    return <div className="skeleton">Loading...</div>;
  }

  if (isLoading) {
    return <div className="spinner">Fetching data...</div>;
  }

  return <div>{JSON.stringify(data)}</div>;
}

// Conditional rendering based on restoration state
function OptimizedComponent() {
  const isRestoring = useIsRestoring();

  return (
    <div>
      {isRestoring ? (
        // Show static content during restoration
        <div className="static-content">
          <h1>Welcome</h1>
          <p>Loading your personalized content...</p>
        </div>
      ) : (
        // Show dynamic content after restoration
        <DynamicContent />
      )}
    </div>
  );
}

// SSR-aware loading states
function SmartLoadingComponent() {
  const isRestoring = useIsRestoring();
  const { data, isLoading, isFetching } = useQuery({
    queryKey: ['smart-data'],
    queryFn: fetchSmartData
  });

  // During restoration, rely on server-rendered content
  if (isRestoring) {
    return data ? (
      <div className="hydrated-content">{data.content}</div>
    ) : (
      <div className="placeholder-content">Content loading...</div>
    );
  }

  // After restoration, show normal loading states
  if (isLoading) {
    return <div className="loading-spinner">Loading...</div>;
  }

  if (isFetching) {
    return (
      <div className="refreshing">
        <div className="content">{data?.content}</div>
        <div className="refresh-indicator">Updating...</div>
      </div>
    );
  }

  return <div className="content">{data?.content}</div>;
}

Custom Hydration Logic

Implementing custom hydration strategies:

function useCustomHydration(dehydratedState: any) {
  const queryClient = useQueryClient();
  const [isHydrated, setIsHydrated] = useState(false);

  useEffect(() => {
    if (!dehydratedState || isHydrated) return;

    // Custom hydration logic
    const hydrateData = async () => {
      try {
        // Validate dehydrated state
        if (!isValidDehydratedState(dehydratedState)) {
          console.warn('Invalid dehydrated state, skipping hydration');
          return;
        }

        // Transform data before hydration if needed
        const transformedState = transformDehydratedState(dehydratedState);

        // Hydrate with custom options
        hydrate(queryClient, transformedState, {
          defaultOptions: {
            queries: {
              staleTime: 30 * 1000, // 30 seconds
              cacheTime: 5 * 60 * 1000, // 5 minutes
            }
          }
        });

        setIsHydrated(true);
      } catch (error) {
        console.error('Custom hydration failed:', error);
      }
    };

    hydrateData();
  }, [dehydratedState, queryClient, isHydrated]);

  return isHydrated;
}

function isValidDehydratedState(state: any): boolean {
  return (
    state &&
    typeof state === 'object' &&
    Array.isArray(state.queries) &&
    Array.isArray(state.mutations)
  );
}

function transformDehydratedState(state: any): any {
  // Transform or filter the state as needed
  return {
    ...state,
    queries: state.queries.filter((query: any) => {
      // Only hydrate recent queries
      return query.state.dataUpdatedAt > Date.now() - 60000;
    })
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-react-query

docs

context.md

error-handling.md

index.md

infinite-queries.md

mutations.md

parallel-queries.md

queries.md

ssr.md

status.md

tile.json