Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

React Hooks for App Bridge

The @launchmystore/app-bridge-react package provides React hooks and components for seamless App Bridge integration in React apps.

Installation

npm install @launchmystore/app-bridge-react

AppBridgeProvider

Wrap your app with AppBridgeProvider to enable App Bridge hooks:
import { AppBridgeProvider } from '@launchmystore/app-bridge-react';

function App() {
  const apiKey = process.env.NEXT_PUBLIC_APP_CLIENT_ID;
  const host = new URLSearchParams(location.search).get('host');
  
  return (
    <AppBridgeProvider apiKey={apiKey} host={host}>
      <MyApp />
    </AppBridgeProvider>
  );
}

Props

PropTypeRequiredDescription
apiKeystringYesYour app’s Client ID
hoststringYesEncoded host from URL params
childrenReactNodeYesChild components

Core Hooks

useAppBridge

Access the App Bridge instance:
import { useAppBridge } from '@launchmystore/app-bridge-react';

function MyComponent() {
  const app = useAppBridge();
  
  const handleClick = () => {
    app.dispatch({
      type: 'TOAST',
      payload: { message: 'Hello!' }
    });
  };
  
  return <button onClick={handleClick}>Say Hello</button>;
}

useSessionToken

Get and manage session tokens:
import { useSessionToken } from '@launchmystore/app-bridge-react';

function MyComponent() {
  const { token, loading, error, refresh } = useSessionToken();
  
  const fetchData = async () => {
    if (!token) return;
    
    const response = await fetch('/api/data', {
      headers: { 'Authorization': `Bearer ${token}` }
    });
    
    return response.json();
  };
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Auth error: {error.message}</div>;
  
  return (
    <div>
      <button onClick={fetchData}>Fetch Data</button>
      <button onClick={refresh}>Refresh Token</button>
    </div>
  );
}
Returns:
FieldTypeDescription
tokenstring | nullCurrent JWT token
loadingbooleanToken is being fetched
errorError | nullToken fetch error
refresh() => voidForce token refresh

UI Hooks

useToast

Show toast notifications:
import { useToast } from '@launchmystore/app-bridge-react';

function MyComponent() {
  const showToast = useToast();
  
  const handleSave = async () => {
    try {
      await saveData();
      showToast('Saved successfully!');
    } catch (error) {
      showToast('Failed to save', { isError: true });
    }
  };
  
  return <button onClick={handleSave}>Save</button>;
}
Options:
showToast(message: string, options?: {
  duration?: number;   // Default 5000ms
  isError?: boolean;   // Error styling
});

useModal

Open confirmation modals:
import { useModal } from '@launchmystore/app-bridge-react';

function DeleteButton({ onDelete }) {
  const { openModal, closeModal } = useModal();
  
  const handleDelete = async () => {
    const confirmed = await openModal({
      title: 'Delete Item?',
      message: 'This action cannot be undone.',
      primaryAction: { content: 'Delete', destructive: true },
      secondaryAction: { content: 'Cancel' }
    });
    
    if (confirmed) {
      await onDelete();
    }
  };
  
  return <button onClick={handleDelete}>Delete</button>;
}
Returns:
FieldTypeDescription
openModal(options) => Promise<boolean>Open modal, returns true if primary clicked
closeModal() => voidClose modal programmatically

useLoading

Control the loading bar:
import { useLoading } from '@launchmystore/app-bridge-react';

function MyComponent() {
  const { startLoading, stopLoading, isLoading } = useLoading();
  
  const handleFetch = async () => {
    startLoading();
    try {
      await fetchData();
    } finally {
      stopLoading();
    }
  };
  
  return (
    <button onClick={handleFetch} disabled={isLoading}>
      {isLoading ? 'Loading...' : 'Fetch Data'}
    </button>
  );
}

useFullscreen

Toggle fullscreen mode:
import { useFullscreen } from '@launchmystore/app-bridge-react';

function VideoPlayer() {
  const { isFullscreen, enterFullscreen, exitFullscreen, toggleFullscreen } = useFullscreen();
  
  return (
    <div>
      <video src="video.mp4" />
      <button onClick={toggleFullscreen}>
        {isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}
      </button>
    </div>
  );
}

useNavigate

Navigate within the admin:
import { useNavigate } from '@launchmystore/app-bridge-react';

function ProductLink({ productId }) {
  const navigate = useNavigate();
  
  const goToProduct = () => {
    navigate(`/admin/products/${productId}`);
  };
  
  const openExternal = () => {
    navigate('https://external.com', { external: true, newTab: true });
  };
  
  return (
    <>
      <button onClick={goToProduct}>View Product</button>
      <button onClick={openExternal}>External Link</button>
    </>
  );
}
Options:
navigate(url: string, options?: {
  newContext?: boolean;  // Leave app context
  external?: boolean;    // External URL
  newTab?: boolean;      // Open in new tab
});

useTitleBar

Update the title bar:
import { useTitleBar } from '@launchmystore/app-bridge-react';

function ProductPage({ product }) {
  const { setTitle, setPrimaryAction, setSecondaryActions, setBreadcrumbs } = useTitleBar();
  
  useEffect(() => {
    setTitle(product.title);
    
    setPrimaryAction({
      content: 'Save',
      onAction: handleSave
    });
    
    setSecondaryActions([
      { content: 'Preview', onAction: handlePreview },
      { content: 'Delete', onAction: handleDelete, destructive: true }
    ]);
    
    setBreadcrumbs([
      { content: 'Products', url: '/products' }
    ]);
    
    return () => {
      // Cleanup on unmount
      setTitle('');
      setPrimaryAction(null);
    };
  }, [product]);
  
  return <div>...</div>;
}

useNavigationMenu

Set up app navigation:
import { useNavigationMenu } from '@launchmystore/app-bridge-react';
import { useLocation } from 'react-router-dom';

function AppNavigation() {
  const location = useLocation();
  const setNavigation = useNavigationMenu();
  
  useEffect(() => {
    setNavigation({
      items: [
        { label: 'Dashboard', destination: '/' },
        { label: 'Reviews', destination: '/reviews' },
        { label: 'Analytics', destination: '/analytics' },
        { label: 'Settings', destination: '/settings' }
      ],
      active: location.pathname
    });
  }, [location.pathname]);
  
  return null;  // This hook handles navigation in the host
}

Resource Picker Hooks

useResourcePicker

Open resource selection modals:
import { useResourcePicker } from '@launchmystore/app-bridge-react';

function ProductSelector({ onSelect }) {
  const pickProducts = useResourcePicker('product');
  
  const handlePick = async () => {
    const selection = await pickProducts({
      multiple: true,
      filter: { status: 'active' }
    });
    
    if (selection) {
      onSelect(selection);
    }
  };
  
  return <button onClick={handlePick}>Select Products</button>;
}
Resource-specific hooks:
import {
  useProductPicker,
  useCollectionPicker,
  useCustomerPicker,
  useOrderPicker,
  useFilePicker
} from '@launchmystore/app-bridge-react';

function MyComponent() {
  const pickProducts = useProductPicker();
  const pickCollections = useCollectionPicker();
  const pickCustomers = useCustomerPicker();
  const pickOrders = useOrderPicker();
  const pickFiles = useFilePicker();
  
  // Each returns (options?) => Promise<selection | null>
}

Save Bar Hook

useContextualSaveBar

Show save/discard bar for unsaved changes:
import { useContextualSaveBar } from '@launchmystore/app-bridge-react';
import { useState, useCallback } from 'react';

function EditForm({ initialData }) {
  const [data, setData] = useState(initialData);
  const [isDirty, setIsDirty] = useState(false);
  
  const { showSaveBar, hideSaveBar } = useContextualSaveBar();
  
  const handleChange = (field, value) => {
    setData({ ...data, [field]: value });
    setIsDirty(true);
    
    showSaveBar({
      onSave: handleSave,
      onDiscard: handleDiscard
    });
  };
  
  const handleSave = useCallback(async () => {
    await saveData(data);
    setIsDirty(false);
    hideSaveBar();
  }, [data]);
  
  const handleDiscard = useCallback(() => {
    setData(initialData);
    setIsDirty(false);
    hideSaveBar();
  }, [initialData]);
  
  return (
    <form>
      <input
        value={data.title}
        onChange={(e) => handleChange('title', e.target.value)}
      />
    </form>
  );
}

Leave Confirmation Hook

useLeaveConfirmation

Warn users about unsaved changes:
import { useLeaveConfirmation } from '@launchmystore/app-bridge-react';

function EditPage() {
  const [isDirty, setIsDirty] = useState(false);
  
  useLeaveConfirmation(
    isDirty,
    'You have unsaved changes. Are you sure you want to leave?'
  );
  
  return <form>...</form>;
}

Utility Hooks

useClipboard

Copy/paste with clipboard:
import { useClipboard } from '@launchmystore/app-bridge-react';

function ShareLink({ url }) {
  const { copy, paste, copied } = useClipboard();
  
  return (
    <div>
      <input value={url} readOnly />
      <button onClick={() => copy(url)}>
        {copied ? 'Copied!' : 'Copy'}
      </button>
    </div>
  );
}

usePrint

Trigger browser print:
import { usePrint } from '@launchmystore/app-bridge-react';

function Invoice() {
  const print = usePrint();
  
  return (
    <div>
      <div className="invoice-content">...</div>
      <button onClick={print}>Print Invoice</button>
    </div>
  );
}

useShare

Open native share dialog (mobile):
import { useShare } from '@launchmystore/app-bridge-react';

function ShareButton({ title, url }) {
  const share = useShare();
  
  return (
    <button onClick={() => share({ title, url })}>
      Share
    </button>
  );
}

useScanner

Open barcode/QR scanner (mobile):
import { useScanner } from '@launchmystore/app-bridge-react';

function InventoryScanner({ onScan }) {
  const { scan, scanning } = useScanner();
  
  const handleScan = async () => {
    const result = await scan();
    if (result) {
      onScan(result.data);
    }
  };
  
  return (
    <button onClick={handleScan} disabled={scanning}>
      {scanning ? 'Scanning...' : 'Scan Barcode'}
    </button>
  );
}

Event Subscription

useAppBridgeSubscription

Subscribe to App Bridge events:
import { useAppBridgeSubscription } from '@launchmystore/app-bridge-react';

function MyComponent() {
  useAppBridgeSubscription('LIFECYCLE_FOCUS', () => {
    // Refresh data when app gains focus
    refetchData();
  });
  
  useAppBridgeSubscription('LIFECYCLE_VISIBILITY', (data) => {
    if (data.visible) {
      // App became visible
    }
  });
  
  return <div>...</div>;
}

Complete Example

import {
  AppBridgeProvider,
  useAppBridge,
  useSessionToken,
  useToast,
  useModal,
  useLoading,
  useNavigate,
  useTitleBar,
  useResourcePicker,
  useContextualSaveBar
} from '@launchmystore/app-bridge-react';

function App() {
  return (
    <AppBridgeProvider 
      apiKey={process.env.NEXT_PUBLIC_APP_CLIENT_ID}
      host={new URLSearchParams(location.search).get('host')}
    >
      <ProductReviews />
    </AppBridgeProvider>
  );
}

function ProductReviews() {
  const [reviews, setReviews] = useState([]);
  const [selectedProduct, setSelectedProduct] = useState(null);
  const [isDirty, setIsDirty] = useState(false);
  
  const { token } = useSessionToken();
  const showToast = useToast();
  const { openModal } = useModal();
  const { startLoading, stopLoading } = useLoading();
  const navigate = useNavigate();
  const pickProduct = useResourcePicker('product');
  const { showSaveBar, hideSaveBar } = useContextualSaveBar();
  const { setTitle, setPrimaryAction } = useTitleBar();
  
  // Set up title bar
  useEffect(() => {
    setTitle('Product Reviews');
    setPrimaryAction({
      content: 'Add Review',
      onAction: handleAddReview
    });
  }, []);
  
  // Handle unsaved changes
  useEffect(() => {
    if (isDirty) {
      showSaveBar({
        onSave: handleSave,
        onDiscard: handleDiscard
      });
    } else {
      hideSaveBar();
    }
  }, [isDirty]);
  
  const handleAddReview = async () => {
    const selection = await pickProduct();
    if (selection && selection.length > 0) {
      setSelectedProduct(selection[0]);
      setIsDirty(true);
    }
  };
  
  const handleSave = async () => {
    startLoading();
    try {
      await fetch('/api/reviews', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ productId: selectedProduct.id })
      });
      
      showToast('Review saved!');
      setIsDirty(false);
    } catch (error) {
      showToast('Failed to save', { isError: true });
    } finally {
      stopLoading();
    }
  };
  
  const handleDiscard = () => {
    setSelectedProduct(null);
    setIsDirty(false);
  };
  
  const handleDelete = async (reviewId) => {
    const confirmed = await openModal({
      title: 'Delete Review?',
      message: 'This action cannot be undone.',
      primaryAction: { content: 'Delete', destructive: true },
      secondaryAction: { content: 'Cancel' }
    });
    
    if (confirmed) {
      await deleteReview(reviewId);
      showToast('Review deleted');
    }
  };
  
  return (
    <div className="reviews-page">
      {reviews.map(review => (
        <ReviewCard 
          key={review.id}
          review={review}
          onDelete={() => handleDelete(review.id)}
        />
      ))}
    </div>
  );
}

TypeScript Support

All hooks are fully typed:
import { useResourcePicker } from '@launchmystore/app-bridge-react';
import type { Product } from '@launchmystore/app-bridge-react';

function ProductPicker() {
  const pickProducts = useResourcePicker<Product>('product');
  
  const handlePick = async () => {
    const selection: Product[] | null = await pickProducts({
      multiple: true
    });
    
    if (selection) {
      selection.forEach(product => {
        console.log(product.title, product.id);
      });
    }
  };
}