React Integration
React Integration
Import the React adapter from @rankhiker/sdk/react (React 18+). The bundle ships with 'use client', so its components and hooks are client components.
npm install @rankhiker/sdk
Browser baseUrl note
These components run in the browser, so always pin the www host or the API key header can be dropped across the apex-to-www redirect:
config={{ apiKey: '...', baseUrl: 'https://rankhiker.com' }}
Provider
Wrap your tree in . Pass either a pre-built client or a config object:
'use client';
import { RankHikerProvider } from '@rankhiker/sdk/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<RankHikerProvider
config={{
apiKey: process.env.NEXT_PUBLIC_RANKHIKER_API_KEY!,
baseUrl: 'https://rankhiker.com',
}}
>
{children}
</RankHikerProvider>
);
}
useRankHikerClient
Returns the client from context. Throws a RankHikerError if there is no provider above it.
import { useRankHikerClient } from '@rankhiker/sdk/react';
const client = useRankHikerClient();
useArticles
useArticles(params?: ListArticlesParams): {
data: RankHikerArticleSummary[] | null;
loading: boolean;
error: RankHikerError | null;
refetch: () => void;
}
Re-runs whenever the params shape changes.
'use client';
import { useArticles } from '@rankhiker/sdk/react';
export function LatestPosts() {
const { data, loading, error, refetch } = useArticles({ limit: 5 });
if (loading) return <p>Loading…</p>;
if (error) return <button onClick={refetch}>Retry</button>;
return (
<ul>
{data?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
useArticle
useArticle(slug: string | null | undefined, params?: GetArticleParams): {
data: RankHikerArticle | null;
loading: boolean;
error: RankHikerError | null;
}
Skips fetching when slug is nullish, so it is safe to call before a route param is ready.
'use client';
import { useArticle } from '@rankhiker/sdk/react';
export function Post({ slug }: { slug: string }) {
const { data, loading, error } = useArticle(slug);
if (loading) return <p>Loading…</p>;
if (error) return <p>Something went wrong.</p>;
if (!data) return <p>Not found.</p>;
return <div dangerouslySetInnerHTML={{ __html: data.content }} />;
}
BlogList
An unstyled list of articles. Props:
| Prop | Type | Notes |
|---|---|---|
params | ListArticlesParams | Filter params for the API call. |
className | string | Class on the grid container. |
cardClassName | string | Class on each card. |
renderCard | (post) => ReactNode | Fully override one card's markup. |
hrefForPost | (post) => string | Slug to URL mapping. Defaults to /blog/{slug}. |
loading | ReactNode | Custom loading state. |
empty | ReactNode | Custom empty state. |
renderError | (error) => ReactNode | Custom error renderer. |
'use client';
import { BlogList } from '@rankhiker/sdk/react';
export function Blog() {
return (
<BlogList
params={{ limit: 12 }}
className="blog-grid"
cardClassName="blog-card"
hrefForPost={(post) => /articles/${post.slug}}
empty={<p>No posts yet.</p>}
/>
);
}
Custom card markup with renderCard:
<BlogList
renderCard={(post) => (
<a href={/articles/${post.slug}} key={post.id}>
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
</a>
)}
/>
BlogPost
Renders a single article by slug. The body HTML is rendered via dangerouslySetInnerHTML unless you pass render. Props:
| Prop | Type | Notes |
|---|---|---|
slug | string | Required. Article slug. |
className | string | Class on the article wrapper. |
loading | ReactNode | Custom loading state. |
notFound | ReactNode | Custom not-found state. |
renderError | (error) => ReactNode | Custom error renderer. |
render | (post) => ReactNode | Fully override how the loaded post renders. |
'use client';
import { BlogPost } from '@rankhiker/sdk/react';
export function PostPage({ slug }: { slug: string }) {
return (
<BlogPost
slug={slug}
className="prose"
notFound={<p>That post does not exist.</p>}
/>
);
}
Full custom render:
<BlogPost
slug={slug}
render={(post) => (
<article>
<h1>{post.title}</h1>
<p>{post.wordCount} words</p>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)}
/>
A full client-side blog (router-agnostic)
This example reads the slug from the URL, so it works with any router. With Next.js App Router, prefer the Server Component approach on the Next.js Integration page and only reach for these client components when you need client-side data fetching.
'use client';
import { useState } from 'react';
import {
RankHikerProvider,
BlogList,
BlogPost,
} from '@rankhiker/sdk/react';
const config = {
apiKey: process.env.NEXT_PUBLIC_RANKHIKER_API_KEY!,
baseUrl: 'https://rankhiker.com',
};
export function Blog() {
const [slug, setSlug] = useState<string | null>(null);
return (
<RankHikerProvider config={config}>
{slug ? (
<>
<button onClick={() => setSlug(null)}>Back</button>
<BlogPost slug={slug} />
</>
) : (
<BlogList
params={{ limit: 12 }}
renderCard={(post) => (
<button key={post.id} onClick={() => setSlug(post.slug)}>
{post.title}
</button>
)}
/>
)}
</RankHikerProvider>
);
}
Was this article helpful?