React Native integration
@media-sdk/native is the React Native hooks layer for the Headless Media SDK. It provides MediaProvider 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.1 @media-sdk/native@^0.1.0 reactPeer dependency: React 18 or 19. @media-sdk/core is installed automatically as a runtime dependency. The hooks package does not require Expo or react-native as a peer — only React.
Two packages for React Native
| Package | Role |
|---|---|
@media-sdk/native | Hooks + MediaProvider — search, pagination, capabilities |
@media-sdk/ui-native | Presentational RN components (PhotoGrid, SearchBar, Modal previews, …) |
Both depend on @media-sdk/core only. Your app wires hooks to UI components (or builds custom UI).
Your React Native app
│
├── @media-sdk/native ← MediaProvider + hooks
│ │
│ ▼
│ @media-sdk/core ← MediaClient, types, cache
│
└── @media-sdk/ui-native ← presentational components (optional)@media-sdk/ui-native peers react-native ≥ 0.71 (global fetch). @media-sdk/native does not — you can use hooks in any React renderer that provides context.
Expo vs bare React Native
The SDK packages are Expo-agnostic. No expo imports in @media-sdk/native or @media-sdk/ui-native.
| Layer | Expo required? |
|---|---|
@media-sdk/core | No |
@media-sdk/native | No |
@media-sdk/ui-native | No |
| Your app | Your choice — Expo example or bare RN CLI |
Quick setup
Create a client from core, wrap your app with MediaProvider, then use hooks in child components:
import { MediaProvider } from "@media-sdk/native";
import {
ApiKeyProvider,
PexelsMediaClient,
} from "@media-sdk/core";
const client = new PexelsMediaClient(
new ApiKeyProvider(process.env.EXPO_PUBLIC_PEXELS_API_KEY!),
);
export function AppRoot({ children }: { children: React.ReactNode }) {
return (
<MediaProvider client={client}>
{children}
</MediaProvider>
);
}import { useMediaSearch } from "@media-sdk/native";
import { Text, View } from "react-native";
function PhotoSearch() {
const { data, loading, error } = useMediaSearch({
query: "nature",
perPage: 20,
});
if (loading) return <Text>Loading…</Text>;
if (error) return <Text>{error.message}</Text>;
return (
<View>
{data?.items.map((photo) => (
<Text key={photo.id}>{photo.photographer}</Text>
))}
</View>
);
}Public API (@media-sdk/native)
import {
MediaProvider,
useMediaCapabilities,
useMediaClient,
useMediaSearch,
useMediaVideos,
type MediaCapabilities,
type UseMediaSearchOptions,
type UseMediaSearchResult,
type UseMediaVideosOptions,
type UseMediaVideosResult,
} from "@media-sdk/native";Internal hooks (usePaginatedMediaSearch, useSearchAbortController) are not exported.
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 |
Hook contracts mirror @media-sdk/react — same options, return shapes, filter forwarding, and abort behavior.
Capability-driven UI
Gate features with useMediaCapabilities, not provider name strings:
import { useMediaCapabilities } from "@media-sdk/native";
function SearchTabs() {
const caps = useMediaCapabilities();
return (
<>
{/* Photos tab always available when searchPhotos is true */}
{caps.operations.searchPhotos && <Tab label="Search" />}
{caps.operations.curatedPhotos && <Tab label="Curated" />}
{caps.operations.searchVideos && <Tab label="Videos" />}
</>
);
}See Capabilities and Filters.
Wiring hooks and UI
import { useMediaSearch } from "@media-sdk/native";
import {
SearchBar,
PhotoGrid,
Pagination,
LoadingState,
ErrorState,
} from "@media-sdk/ui-native";
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={() => 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}
accessibilityLabel="Photo results"
onPrevious={() => void previousPage()}
onNext={() => void nextPage()}
/>
</>
)}
</>
);
}Next steps
- Installation — packages, peer deps, API keys
- MediaProvider — context setup and
useMediaClient - UI components —
@media-sdk/ui-nativereference - Expo example —
examples/basic-react-native - Troubleshooting — common issues