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.
pnpm add @media-sdk/core@^0.3.0 @media-sdk/react@^0.3.0 reactPeer dependency: React 18 or 19. @media-sdk/core is installed automatically as a runtime dependency.
When to use @media-sdk/react
| Use case | Package |
|---|---|
| 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 MediaProvider — PexelsMediaClient, PixabayMediaClient, or a custom implementation.
Quick setup
Create a client from core, wrap your app with MediaProvider, then use hooks in child components:
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>
);
}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
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
| Hook | Purpose |
|---|---|
MediaProvider | Supplies a MediaClient to the React tree |
useMediaClient | Read the shared client from context |
useMediaCapabilities | Read provider capabilities for UI gating |
useMediaSearch | Photo search with loading, error, and pagination |
useMediaVideos | Video 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:
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:
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
- Hooks reference —
MediaProvider, all hooks, filters,enabled, errors - UI components — presentational-only philosophy and prop tables
- Capabilities —
getCapabilities, capability matrix - Filters —
photoFilters/videoFilters - Cancellation — abort behavior and
isAbortError - Live demo — reference app in
apps/web