Consuming the GraphQL API in Sana Frontend Applications
Introduction
This document provides a comprehensive guide for frontend developers on the established patterns for consuming the Sana GraphQL API. The appropriate method depends on the target environment: the Admin UI or the Webstore UI.
- For the Admin UI, all data fetching must be handled through Redux-Observable Epics.
 - For the Webstore UI, developers have two options: the useApi hook for simple, component-scoped data, or epics for more complex, global state management.
 
Understanding these environment-specific constraints is key to developing robust and maintainable features within the Sana ecosystem.
Choosing the Right Data-Fetching Strategy
Selecting the appropriate strategy is a critical architectural decision that impacts the scalability and maintainability of your feature. The table below provides a high-level comparison to guide your decision.
| Feature | useApi Hook | Epics | 
|---|---|---|
| Availability | Webstore UI Only | Admin UI & Webstore UI | 
| Primary Use Case | Simple "fetch-and-display" in a single component. | Complex asynchronous flows and shared application state. | 
| State Scope | Local to the component (useState, useReducer). | Global Redux state, accessible by any component. | 
| Complexity | Low. Uses standard React patterns. | High. Requires knowledge of RxJS and Redux-Observable. | 
| Decoupling | Logic is coupled with the component lifecycle. | API logic is fully decoupled from the UI. | 
| Testability | Component logic must be tested as a whole. | Business logic can be tested in isolation. | 
| Best For | A Sana Content Block fetching its own data. | Required for all Admin UI data fetching; used for shared data in the Webstore. | 
Method 1: The useApi Hook (Webstore UI Only)
This is the most direct method for fetching data within a single React component or Sana Content Block. This hook is only available in the Webstore UI.
Hook Signature
The hook accepts no parameters and returns the application's Api object.
import { useApi } from '@sana/adk/framework';
const api = useApi(): Api;
Example
This example demonstrates fetching a list of news when a component mounts and managing the state locally.
import React, { useState, useEffect } from 'react';
import { useApi } from '@sana/adk/framework';
// 1. Define the GraphQL query as a constant.
const NEWS_QUERY = `
  query NewsItemsQuery($page: PagingInput){
    # Use GraphQL Query Variables to inject PagingInput
    news{
        list(page: $page){
         items{
            title
            shortDescription
          }
      }
    }
}
`;
// Define a type for the component's state for type safety.
type News = {
  title: string
  shortDescription: string
};
const NewListComponent: React.FC = () => {
  // 2. Access the api object from the ADK framework.
  const api = useApi();
  // 3. Manage local state for data, loading, and potential errors.
  const [news, setNews] = useState<News[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  useEffect(() => {
    // 4. Call api.graphApi, which returns an RxJS Observable.
    const subscription = api.graphApi(NEWS_QUERY).subscribe({
      next: (response) => {
        // 5. On a successful response, update the state.
        setNews(response.data.news);
        setIsLoading(false);
      },
      error: (err) => {
        // 6. On failure, capture the error message.
        console.error('Failed to fetch news:', err);
        setError('An error occurred while fetching data.');
        setIsLoading(false);
      },
    });
    // 7. Return a cleanup function to unsubscribe on component unmount.
    // This prevents memory leaks and race conditions.
    return () => subscription.unsubscribe();
  }, [api]); // The dependency array ensures this effect runs only once.
  if (isLoading) {
    return <div>Loading News...</div>;
  }
  if (error) {
    return <div style={{ color: 'red' }}>{error}</div>;
  }
  return (
    <ul>
      {news.map((nw) => (
        <li key={nw.title}>{nw.shortDescription}</li>
      ))}
    </ul>
  );
};
Best Practices and Use Cases
- Single Component Data: Use this method when data is not needed outside of the component that fetches it.
 - Simplicity: Ideal for straightforward data requirements where the overhead of global state management is unnecessary.
 - Lifecycle Management: Always manage the subscription lifecycle within useEffect, ensuring you unsubscribe in the cleanup function.
 - Error Handling: Implement error handling within the subscribe call to gracefully manage API failures.
 
Method 2: Epics for Centralized State Management (Admin UI & Webstore UI)
This pattern decouples API logic from your components, improves testability, and centralizes state management in Redux. It is the only approved method for data fetching in the Admin UI and is also used in the Webstore for managing complex, shared state.
The typical data flow is: Component Dispatches Action → Epic Listens for Action → Epic Makes API Call → Epic Dispatches Success/Failure Action → Reducer Updates State → Component Re-renders
Step 1: Define the GraphQL Query
For better organization and reusability, define queries in a dedicated file (e.g., src/behavior/queries.ts).
// src/behavior/queries.ts
export const NEWS_ITEMS_QUERY = `
  query NewsItemsQuery($page: PagingInput) {
    # GraphQL Query Variables are used to pass dynamic values like pagination.
    news {
      list(page: $page) {
        items {
          title
          shortDescription
        }
      }
    }
  }
`;
Step 2: Implement the Epic
The epic listens for a starting action, performs the asynchronous API call, and dispatches corresponding success or failure actions.
// src/behavior/epic.ts
import { ofType, Epic } from 'redux-observable';
import { mergeMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { NEWS_ITEMS_QUERY } from './queries';
import { fetchNewsSuccess, fetchNewsFailure, FETCH_NEWS_START } from './actions'; // Action creators and types
// The epic receives the action stream, state stream, and dependencies like the api object.
const fetchNewsEpic: Epic = (action$, _state$, { api }) =>
  action$.pipe(
    // 1. Listen for the specific action that triggers this flow.
    ofType(FETCH_NEWS_START),
    // 2. Use mergeMap to handle the inner observable from the API call.
    mergeMap((action) =>
      // 3. Call the graphApi with the query and variables from the action payload.
      api.graphApi(NEWS_ITEMS_QUERY, { page: action.payload.page }).pipe(
        // 4. On success, map the response to a 'success' action.
        map((response) => fetchNewsSuccess(response.data.news.list.items)),
        // 5. On failure, use catchError to dispatch a 'failure' action.
        // It's critical to return a new observable (e.g., with of())
        // to prevent the epic from terminating.
        catchError((error) => of(fetchNewsFailure(error.message)))
      )
    )
  );
Best Practices and Use Cases
- Shared State: Use epics when data needs to be accessed by multiple, disconnected components.
 - Complex Flows: Ideal for handling business logic that involves multiple steps, side effects, or dependencies on other application states.
 - Decoupling: Separates the concerns of data fetching (epics) from data presentation (React components), leading to cleaner, more maintainable code.
 - Robust Error Handling: Always use the catchError operator to handle API failures gracefully and prevent the epic stream from terminating unexpectedly.