Skip to content

React integration

@media-sdk/react is the React layer for the Headless Media SDK. It provides a context provider and hooks for photo and video search, pagination, capability discovery, and client access — without owning HTTP transport, caching, or presentational UI.

bash
pnpm add @media-sdk/core@^0.3.0 @media-sdk/react@^0.3.0 react

Peer dependency: React 18 or 19. @media-sdk/core is installed automatically as a runtime dependency.

When to use @media-sdk/react

Use casePackage
Direct API calls, scripts, or non-React apps@media-sdk/core
React hooks for search, pagination, and client context@media-sdk/react (this guide)
Presentational grids, search bar, previews, pagination UI@media-sdk/ui-react

@media-sdk/react does not render UI. Pair it with @media-sdk/ui-react for ready-made components, or build your own UI and pass hook results as props.

Architecture

Your React app

       ├── @media-sdk/react      ← MediaProvider + hooks
       │         │
       │         ▼
       │   @media-sdk/core       ← MediaClient, types, cache

       └── @media-sdk/ui-react   ← presentational components (optional)

Hooks call into whatever MediaClient you pass to MediaProviderPexelsMediaClient, PixabayMediaClient, or a custom implementation.

Quick setup

Create a client from core, wrap your app with MediaProvider, then use hooks in child components:

tsx
import { MediaProvider } from "@media-sdk/react";
import {
  ApiKeyProvider,
  PexelsMediaClient,
} from "@media-sdk/core";

const client = new PexelsMediaClient(
  new ApiKeyProvider(import.meta.env.VITE_PEXELS_API_KEY),
);

export function AppRoot({ children }: { children: React.ReactNode }) {
  return (
    <MediaProvider client={client}>
      {children}
    </MediaProvider>
  );
}
tsx
import { useMediaSearch } from "@media-sdk/react";

function PhotoSearch() {
  const { data, loading, error } = useMediaSearch({
    query: "nature",
    perPage: 20,
  });

  if (loading) return <p>Loading…</p>;
  if (error) return <p>{error.message}</p>;

  return (
    <ul>
      {data?.items.map((photo) => (
        <li key={photo.id}>{photo.photographer}</li>
      ))}
    </ul>
  );
}

Public API

ts
import {
  MediaProvider,
  useMediaCapabilities,
  useMediaClient,
  useMediaSearch,
  useMediaVideos,
  type UseMediaSearchOptions,
  type UseMediaSearchResult,
  type UseMediaVideosOptions,
  type UseMediaVideosResult,
} from "@media-sdk/react";

MediaContext is internal and not part of the public API.

Hooks overview

HookPurpose
MediaProviderSupplies a MediaClient to the React tree
useMediaClientRead the shared client from context
useMediaCapabilitiesRead provider capabilities for UI gating
useMediaSearchPhoto search with loading, error, and pagination
useMediaVideosVideo search with the same contract as useMediaSearch

See the hooks reference for options, return values, filter forwarding, enabled, and error handling.

Capability-driven UI

Gate features with useMediaCapabilities, not provider name strings:

tsx
import { useMediaCapabilities } from "@media-sdk/react";

function SearchTabs() {
  const caps = useMediaCapabilities();

  return (
    <>
      <Tab>Search</Tab>
      {caps.operations.curatedPhotos && <Tab>Curated</Tab>}
    </>
  );
}

Check caps.photoFilters / caps.videoFilters before rendering filter controls or passing filter values to search hooks. See Capabilities and Filters.

Wiring hooks and UI

A typical app composes @media-sdk/react hooks with @media-sdk/ui-react components:

tsx
import { useMediaSearch } from "@media-sdk/react";
import {
  SearchBar,
  PhotoGrid,
  Pagination,
  LoadingState,
  ErrorState,
} from "@media-sdk/ui-react";
import { useState } from "react";

function PhotoBrowser() {
  const [query, setQuery] = useState("");
  const [submittedQuery, setSubmittedQuery] = useState("");

  const { data, loading, error, nextPage, previousPage } = useMediaSearch({
    query: submittedQuery,
    enabled: submittedQuery.length > 0,
  });

  return (
    <>
      <SearchBar
        value={query}
        onChange={setQuery}
        onSubmit={(event) => {
          event.preventDefault();
          setSubmittedQuery(query);
        }}
      />

      {loading && <LoadingState message="Loading photos…" />}
      {error && <ErrorState message={error.message} />}

      {data && (
        <>
          <PhotoGrid photos={data.items} onPhotoSelect={() => {}} />
          <Pagination
            page={data.pagination.page}
            hasPrevious={data.pagination.hasPrevious}
            hasNext={data.pagination.hasNext}
            loading={loading}
            ariaLabel="Photo results"
            onPrevious={() => void previousPage()}
            onNext={() => void nextPage()}
          />
        </>
      )}
    </>
  );
}

Full component prop tables: UI components.

Next steps