You are viewing a preview of this lesson. Sign in to start learning
Back to Mastering Relay

usePreloadedQuery Pattern

Separating query loading from rendering for optimal performance

usePreloadedQuery Pattern

Master the usePreloadedQuery pattern with free flashcards and spaced repetition practice. This lesson covers preloading queries before render, the usePreloadedQuery hook, and how to integrate preloaded queries into your component architectureβ€”essential concepts for building performant Relay applications.

Welcome to Preloading Queries πŸ’»

The usePreloadedQuery pattern is one of Relay's most powerful performance optimizations. Instead of waiting until your component renders to start fetching data, you can begin loading data before your component even mounts. This pattern dramatically reduces perceived loading times and creates snappier user experiences.

Think of it like ordering food ahead of time. Without preloading, you walk into a restaurant, sit down, look at the menu, and then orderβ€”waiting while your food is prepared. With preloading, you call ahead with your order, so when you arrive, your food is already being prepared or ready to go. The result? Less waiting, happier customers (users).

Core Concepts: How Preloading Works πŸ”„

The Traditional Flow vs. Preloading

In a traditional data fetching pattern, the sequence looks like this:

TRADITIONAL FLOW (Sequential)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                         β”‚
β”‚  1. User clicks button                  β”‚
β”‚           ↓                             β”‚
β”‚  2. Component starts rendering          β”‚
β”‚           ↓                             β”‚
β”‚  3. Component requests data             β”‚
β”‚           ↓                             β”‚
β”‚  4. Wait for network response... ⏰     β”‚
β”‚           ↓                             β”‚
β”‚  5. Component renders with data βœ…      β”‚
β”‚                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     Total Time: Render Time + Network Time

With the usePreloadedQuery pattern, the flow changes dramatically:

PRELOADING FLOW (Parallel)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                         β”‚
β”‚  1. User clicks button                  β”‚
β”‚           ↓                             β”‚
β”‚  2. Immediately start data fetch πŸš€     β”‚
β”‚           ↓                             β”‚
β”‚  3. Component starts rendering          β”‚
β”‚           ↓                             β”‚
β”‚  4. Component receives preloaded data βœ…β”‚
β”‚                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     Total Time: Max(Render Time, Network Time)

The network request happens in parallel with navigation and component initialization, potentially saving hundreds of milliseconds.

The Three Key Players 🎭

1. loadQuery() - The Initiator

This function starts the data fetching process. You call it in response to user actions (clicks, hovers, route changes) or during application initialization.

import { loadQuery } from 'react-relay';
import { environment } from './RelayEnvironment';
import UserProfileQuery from './__generated__/UserProfileQuery.graphql';

// Start loading immediately
const queryReference = loadQuery(
  environment,
  UserProfileQuery,
  { userID: '123' },
  { fetchPolicy: 'store-or-network' }
);

Key characteristics:

  • Returns a queryReference (not the data itself)
  • Starts the network request immediately
  • Can be called outside of React components
  • Can be called before components even mount

2. queryReference - The Token

The queryReference is like a ticket stub for your data. It doesn't contain the dataβ€”it's a reference to the in-flight or completed request.

// This is NOT data - it's a reference!
const queryReference = loadQuery(...);

// You pass this reference to components
<UserProfile queryReference={queryReference} />

πŸ’‘ Think of queryReference as a claim ticket: When you drop off dry cleaning, they give you a ticket. The ticket isn't your clothesβ€”it's proof that you can pick them up. Similarly, queryReference isn't your dataβ€”it's a token you exchange for data when you're ready.

3. usePreloadedQuery() - The Consumer

Inside your component, this hook exchanges the queryReference for actual data:

import { usePreloadedQuery } from 'react-relay';
import { graphql } from 'relay-runtime';

function UserProfile({ queryReference }) {
  const data = usePreloadedQuery(
    graphql`
      query UserProfileQuery($userID: ID!) {
        user(id: $userID) {
          name
          email
          avatar
        }
      }
    `,
    queryReference
  );

  return (
    <div>
      <img src={data.user.avatar} alt="Avatar" />
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
    </div>
  );
}

Key behaviors:

  • If data is ready: returns immediately
  • If data is still loading: suspends (use React Suspense)
  • If error occurred: throws error (use Error Boundary)

The Complete Pattern: Router Integration πŸ—ΊοΈ

The most powerful use of usePreloadedQuery is integrating it with your router. Here's how to preload data during navigation:

Step 1: Create Route Configuration
// routes.js
import { loadQuery } from 'react-relay';
import { environment } from './RelayEnvironment';

const routes = [
  {
    path: '/user/:userID',
    component: UserProfile,
    prepare: (params) => {
      const UserProfileQuery = require('./__generated__/UserProfileQuery.graphql');
      return {
        userProfileQuery: loadQuery(
          environment,
          UserProfileQuery,
          { userID: params.userID }
        )
      };
    }
  },
  {
    path: '/posts/:postID',
    component: PostDetail,
    prepare: (params) => {
      const PostDetailQuery = require('./__generated__/PostDetailQuery.graphql');
      return {
        postQuery: loadQuery(
          environment,
          PostDetailQuery,
          { postID: params.postID }
        )
      };
    }
  }
];
Step 2: Preload on Navigation Events
// Router.js
function Router() {
  const [currentRoute, setCurrentRoute] = useState(null);
  const [queryRefs, setQueryRefs] = useState({});

  const navigate = (path, params) => {
    const route = routes.find(r => matchPath(path, r.path));
    
    // Start loading BEFORE changing route!
    const preparedData = route.prepare(params);
    
    // Update state with new route and query references
    setQueryRefs(preparedData);
    setCurrentRoute({ route, params });
  };

  if (!currentRoute) return <div>Loading...</div>;

  const RouteComponent = currentRoute.route.component;
  
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <RouteComponent {...queryRefs} />
    </Suspense>
  );
}
Step 3: Consume in Component
// UserProfile.js
function UserProfile({ userProfileQuery }) {
  const data = usePreloadedQuery(
    graphql`
      query UserProfileQuery($userID: ID!) {
        user(id: $userID) {
          id
          name
          email
          bio
          followers {
            count
          }
          posts {
            edges {
              node {
                id
                title
              }
            }
          }
        }
      }
    `,
    userProfileQuery // queryReference passed as prop
  );

  return (
    <div className="profile">
      <h1>{data.user.name}</h1>
      <p>{data.user.bio}</p>
      <div>Followers: {data.user.followers.count}</div>
      <PostList posts={data.user.posts.edges} />
    </div>
  );
}

Example 1: Preloading on Click Events πŸ–±οΈ

One of the simplest applications is preloading when a user hovers over or clicks a link:

import { useState } from 'react';
import { loadQuery, usePreloadedQuery } from 'react-relay';
import { graphql } from 'relay-runtime';
import { environment } from './RelayEnvironment';

// The query definition
const ProductQuery = graphql`
  query ProductDetailQuery($productID: ID!) {
    product(id: $productID) {
      id
      name
      description
      price
      images {
        url
      }
      reviews {
        rating
        count
      }
    }
  }
`;

function ProductLink({ productID }) {
  const [queryRef, setQueryRef] = useState(null);
  const [showDetail, setShowDetail] = useState(false);

  const handleMouseEnter = () => {
    // Start loading on hover - data may be ready by click time!
    if (!queryRef) {
      const ref = loadQuery(
        environment,
        ProductQuery,
        { productID }
      );
      setQueryRef(ref);
    }
  };

  const handleClick = () => {
    // Ensure query is started
    if (!queryRef) {
      const ref = loadQuery(
        environment,
        ProductQuery,
        { productID }
      );
      setQueryRef(ref);
    }
    setShowDetail(true);
  };

  return (
    <>
      <button
        onMouseEnter={handleMouseEnter}
        onClick={handleClick}
      >
        View Product Details
      </button>
      
      {showDetail && queryRef && (
        <Suspense fallback={<div>Loading product...</div>}>
          <ProductDetail queryReference={queryRef} />
        </Suspense>
      )}
    </>
  );
}

function ProductDetail({ queryReference }) {
  const data = usePreloadedQuery(ProductQuery, queryReference);
  
  return (
    <div className="product-detail">
      <img src={data.product.images[0].url} alt={data.product.name} />
      <h2>{data.product.name}</h2>
      <p>{data.product.description}</p>
      <div className="price">${data.product.price}</div>
      <div className="rating">
        Rating: {data.product.reviews.rating} ({data.product.reviews.count} reviews)
      </div>
    </div>
  );
}

Why this works so well:

  • Hover typically happens 100-500ms before click
  • Network request gets a head start
  • By click time, data might already be available
  • User perceives instant loading

Example 2: Preloading Multiple Queries πŸ”—

Complex pages often need multiple data sources. You can preload them all simultaneously:

import { loadQuery } from 'react-relay';
import { environment } from './RelayEnvironment';

// Multiple queries
const DashboardUserQuery = require('./__generated__/DashboardUserQuery.graphql');
const DashboardStatsQuery = require('./__generated__/DashboardStatsQuery.graphql');
const DashboardNotificationsQuery = require('./__generated__/DashboardNotificationsQuery.graphql');

function prepareDashboard(userID) {
  // Load all queries in parallel!
  return {
    userQuery: loadQuery(
      environment,
      DashboardUserQuery,
      { userID }
    ),
    statsQuery: loadQuery(
      environment,
      DashboardStatsQuery,
      { userID, period: 'LAST_30_DAYS' }
    ),
    notificationsQuery: loadQuery(
      environment,
      DashboardNotificationsQuery,
      { userID, limit: 10 }
    )
  };
}

// Usage in component
function Dashboard({ userQuery, statsQuery, notificationsQuery }) {
  const userData = usePreloadedQuery(
    graphql`
      query DashboardUserQuery($userID: ID!) {
        user(id: $userID) {
          name
          avatar
        }
      }
    `,
    userQuery
  );

  const statsData = usePreloadedQuery(
    graphql`
      query DashboardStatsQuery($userID: ID!, $period: Period!) {
        stats(userID: $userID, period: $period) {
          pageViews
          revenue
          conversions
        }
      }
    `,
    statsQuery
  );

  const notificationsData = usePreloadedQuery(
    graphql`
      query DashboardNotificationsQuery($userID: ID!, $limit: Int!) {
        notifications(userID: $userID, limit: $limit) {
          id
          message
          timestamp
        }
      }
    `,
    notificationsQuery
  );

  return (
    <div className="dashboard">
      <header>
        <img src={userData.user.avatar} alt="Avatar" />
        <h1>Welcome, {userData.user.name}</h1>
      </header>
      
      <section className="stats">
        <StatCard label="Page Views" value={statsData.stats.pageViews} />
        <StatCard label="Revenue" value={`$${statsData.stats.revenue}`} />
        <StatCard label="Conversions" value={statsData.stats.conversions} />
      </section>
      
      <section className="notifications">
        <h2>Recent Notifications</h2>
        {notificationsData.notifications.map(n => (
          <NotificationItem key={n.id} notification={n} />
        ))}
      </section>
    </div>
  );
}

Performance benefit: All three queries execute simultaneously on the server, rather than sequentially. If each query takes 200ms, you save 400ms compared to waterfall loading!

WATERFALL (Sequential):
User Query     [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 200ms
Stats Query              [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 200ms
Notifications                     [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 200ms
Total: 600ms

PARALLEL (Preloaded):
User Query     [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 200ms
Stats Query    [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 200ms
Notifications  [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 200ms
Total: 200ms

⚑ 3x faster!

Example 3: Conditional Preloading 🎯

Sometimes you want to preload only under certain conditions:

import { loadQuery } from 'react-relay';
import { environment } from './RelayEnvironment';

function SmartArticleLink({ articleID, isPremiumUser }) {
  const [queryRef, setQueryRef] = useState(null);

  const handleHover = () => {
    // Only preload full content for premium users
    // Free users will see a preview that loads differently
    if (isPremiumUser && !queryRef) {
      const ArticleQuery = require('./__generated__/ArticleFullQuery.graphql');
      const ref = loadQuery(
        environment,
        ArticleQuery,
        { 
          articleID,
          includePremiumContent: true 
        },
        {
          fetchPolicy: 'store-or-network',
          // Cache premium content longer
          networkCacheConfig: { force: false }
        }
      );
      setQueryRef(ref);
    } else if (!isPremiumUser && !queryRef) {
      const ArticlePreviewQuery = require('./__generated__/ArticlePreviewQuery.graphql');
      const ref = loadQuery(
        environment,
        ArticlePreviewQuery,
        { articleID }
      );
      setQueryRef(ref);
    }
  };

  return (
    <div onMouseEnter={handleHover}>
      <ArticleTitle articleID={articleID} />
      {queryRef && (
        <Suspense fallback={<Spinner />}>
          {isPremiumUser ? (
            <ArticleFull queryReference={queryRef} />
          ) : (
            <ArticlePreview queryReference={queryRef} />
          )}
        </Suspense>
      )}
    </div>
  );
}

Example 4: Retry and Error Handling πŸ”„

Handling errors with preloaded queries requires careful consideration:

import { loadQuery, usePreloadedQuery } from 'react-relay';
import { useState, useEffect } from 'react';
import { environment } from './RelayEnvironment';

function UserProfileContainer({ userID }) {
  const [queryRef, setQueryRef] = useState(null);
  const [loadError, setLoadError] = useState(null);

  useEffect(() => {
    // Load query with error handling
    try {
      const ref = loadQuery(
        environment,
        UserProfileQuery,
        { userID },
        {
          fetchPolicy: 'network-only',
          onError: (error) => {
            console.error('Query failed to load:', error);
            setLoadError(error);
          }
        }
      );
      setQueryRef(ref);
      setLoadError(null);
    } catch (error) {
      setLoadError(error);
    }
  }, [userID]);

  const handleRetry = () => {
    // Clear error and try again
    setLoadError(null);
    const ref = loadQuery(
      environment,
      UserProfileQuery,
      { userID },
      { fetchPolicy: 'network-only' }
    );
    setQueryRef(ref);
  };

  if (loadError) {
    return (
      <div className="error-state">
        <p>Failed to load user profile: {loadError.message}</p>
        <button onClick={handleRetry}>Retry</button>
      </div>
    );
  }

  if (!queryRef) {
    return <div>Preparing to load...</div>;
  }

  return (
    <ErrorBoundary fallback={<ErrorMessage onRetry={handleRetry} />}>
      <Suspense fallback={<ProfileSkeleton />}>
        <UserProfile queryReference={queryRef} />
      </Suspense>
    </ErrorBoundary>
  );
}

function UserProfile({ queryReference }) {
  const data = usePreloadedQuery(
    graphql`
      query UserProfileQuery($userID: ID!) {
        user(id: $userID) {
          name
          email
          posts { count }
        }
      }
    `,
    queryReference
  );

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
      <p>Posts: {data.user.posts.count}</p>
    </div>
  );
}

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
}

Common Mistakes ⚠️

1. Calling loadQuery Inside Components Without Proper Memoization

❌ Wrong:

function MyComponent() {
  // This creates a new query on EVERY render!
  const queryRef = loadQuery(
    environment,
    MyQuery,
    { id: '123' }
  );
  // ...
}

βœ… Right:

function MyComponent() {
  const [queryRef, setQueryRef] = useState(() => 
    loadQuery(environment, MyQuery, { id: '123' })
  );
  // Query loads only once
}

2. Not Using Suspense

The usePreloadedQuery hook will suspend if data isn't ready. You must wrap it with Suspense:

❌ Wrong:

function App() {
  return <UserProfile queryReference={queryRef} />;
  // Will crash if data not ready!
}

βœ… Right:

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <UserProfile queryReference={queryRef} />
    </Suspense>
  );
}

3. Reusing Query References Incorrectly

Query references are tied to specific variables. Don't reuse them with different parameters:

❌ Wrong:

const queryRef = loadQuery(environment, UserQuery, { id: '1' });
// Later, trying to use same ref for different user
<UserProfile queryReference={queryRef} userID="2" />
// Still shows user 1's data!

βœ… Right:

const queryRef1 = loadQuery(environment, UserQuery, { id: '1' });
const queryRef2 = loadQuery(environment, UserQuery, { id: '2' });
// Use appropriate reference for each user

4. Forgetting to Pass Query Reference as Prop

❌ Wrong:

const queryRef = loadQuery(...);
<MyComponent />  // Forgot to pass queryRef!

βœ… Right:

const queryRef = loadQuery(...);
<MyComponent queryReference={queryRef} />

5. Mixing usePreloadedQuery with useLazyLoadQuery

❌ Wrong:

function MyComponent({ queryReference }) {
  // Don't use useLazyLoadQuery when you have a queryReference!
  const data = useLazyLoadQuery(MyQuery, variables);
}

βœ… Right:

function MyComponent({ queryReference }) {
  const data = usePreloadedQuery(MyQuery, queryReference);
}

πŸ’‘ Rule of thumb: If you have a queryReference, use usePreloadedQuery. If you need to load data during render, use useLazyLoadQuery.

Advanced: Disposing Query References πŸ—‘οΈ

Query references hold resources in memory. For long-lived applications, you should dispose of them:

import { useEffect, useState } from 'react';
import { loadQuery } from 'react-relay';

function SearchResults({ searchTerm }) {
  const [queryRef, setQueryRef] = useState(null);

  useEffect(() => {
    const SearchQuery = require('./__generated__/SearchQuery.graphql');
    const ref = loadQuery(
      environment,
      SearchQuery,
      { term: searchTerm }
    );
    setQueryRef(ref);

    // Cleanup function
    return () => {
      // Dispose of query reference when component unmounts
      // or when searchTerm changes
      ref.dispose();
    };
  }, [searchTerm]);

  if (!queryRef) return <div>Loading...</div>;

  return (
    <Suspense fallback={<SearchingSkeleton />}>
      <SearchResultsList queryReference={queryRef} />
    </Suspense>
  );
}

Fetch Policies with Preloaded Queries πŸ“‹

You can control caching behavior when calling loadQuery:

Fetch Policy Behavior Use Case
store-or-network Use cache if available, otherwise network Default - balance speed and freshness
store-and-network Use cache immediately, then fetch fresh Show stale data fast, update when fresh arrives
network-only Always fetch from network, ignore cache Ensure absolutely fresh data
store-only Only use cache, never network Offline mode, or when data known to exist
const queryRef = loadQuery(
  environment,
  MyQuery,
  variables,
  {
    fetchPolicy: 'store-and-network',
    // Show cached data immediately, but also fetch fresh
  }
);

Key Takeaways 🎯

  1. usePreloadedQuery requires three steps: Call loadQuery() to get a queryReference, pass it to your component, and consume it with usePreloadedQuery()

  2. Load data as early as possible: The earlier you call loadQuery(), the sooner data arrives. Ideal locations: route changes, hover events, or app initialization

  3. queryReference is not data: It's a token representing an in-flight or completed request. Don't try to read it directly

  4. Always use Suspense: usePreloadedQuery will suspend if data isn't ready. Wrap components with Suspense boundaries

  5. Parallel loading wins: When loading multiple queries, they execute simultaneously. This is much faster than sequential loading

  6. Dispose when done: Call .dispose() on query references in cleanup functions to free memory

  7. Match query to reference: Each queryReference is tied to specific variables. Create new references for different parameters

  8. Choose the right fetch policy: Control whether to prefer cache or network based on your data freshness requirements

Performance Comparison πŸ“Š

Here's a real-world comparison of loading patterns:

Pattern Time to Interactive User Perception
useLazyLoadQuery (render-time) ~800ms "Slow"
usePreloadedQuery (on click) ~500ms "Acceptable"
usePreloadedQuery (on hover) ~200ms "Fast"
usePreloadedQuery (route change) ~100ms "Instant"

πŸ’‘ Did you know? Studies show users perceive actions taking under 100ms as "instantaneous." Preloading can get you into that golden zone!

πŸ”§ Try This: Build a Smart Navigation System

Implement a navigation system that preloads the next likely page:

function SmartNav({ currentPage }) {
  const [preloadedPages, setPreloadedPages] = useState({});

  useEffect(() => {
    // Predict and preload next likely pages
    const predictions = predictNextPages(currentPage);
    
    predictions.forEach(pageName => {
      if (!preloadedPages[pageName]) {
        const query = getQueryForPage(pageName);
        const ref = loadQuery(environment, query, {});
        setPreloadedPages(prev => ({
          ...prev,
          [pageName]: ref
        }));
      }
    });
  }, [currentPage]);

  return (
    <nav>
      {/* Navigation links with instant data */}
    </nav>
  );
}

function predictNextPages(current) {
  // Predict based on analytics, user behavior, common flows
  const predictions = {
    'home': ['products', 'about'],
    'products': ['product-detail', 'cart'],
    'cart': ['checkout', 'products'],
  };
  return predictions[current] || [];
}

πŸ“š Further Study

Deepen your understanding with these resources:

πŸ“‹ Quick Reference Card: usePreloadedQuery Pattern

loadQuery() Initiates data fetch, returns queryReference
queryReference Token representing in-flight or completed request
usePreloadedQuery() Exchanges queryReference for data in component
When to call loadQuery Route change, hover, click, or app init
Required wrapper Suspense boundary (for loading states)
Cleanup Call queryRef.dispose() in useEffect cleanup
Performance gain Parallel loading vs sequential (up to 3x faster)
Best practices Load early, dispose properly, use Suspense, match vars to refs
FLOW DIAGRAM:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ User Action  β”‚
β”‚ (hover/click)β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ loadQuery()          β”‚
β”‚ Returns queryRef     β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Pass to Component    β”‚
β”‚  β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ usePreloadedQuery()  β”‚
β”‚ Returns data         β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Render with Data βœ…  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜