SDK & Export API

React Integration

Last updated June 4, 2026

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.

bash
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:

tsx
config={{ apiKey: '...', baseUrl: 'https://rankhiker.com' }}

Provider

Wrap your tree in . Pass either a pre-built client or a config object:

tsx
'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.

tsx
import { useRankHikerClient } from '@rankhiker/sdk/react';

const client = useRankHikerClient();

useArticles

ts
useArticles(params?: ListArticlesParams): {
  data: RankHikerArticleSummary[] | null;
  loading: boolean;
  error: RankHikerError | null;
  refetch: () => void;
}

Re-runs whenever the params shape changes.

tsx
'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

ts
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.

tsx
'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:

PropTypeNotes
paramsListArticlesParamsFilter params for the API call.
classNamestringClass on the grid container.
cardClassNamestringClass on each card.
renderCard(post) => ReactNodeFully override one card's markup.
hrefForPost(post) => stringSlug to URL mapping. Defaults to /blog/{slug}.
loadingReactNodeCustom loading state.
emptyReactNodeCustom empty state.
renderError(error) => ReactNodeCustom error renderer.
tsx
'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:

tsx
<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:

PropTypeNotes
slugstringRequired. Article slug.
classNamestringClass on the article wrapper.
loadingReactNodeCustom loading state.
notFoundReactNodeCustom not-found state.
renderError(error) => ReactNodeCustom error renderer.
render(post) => ReactNodeFully override how the loaded post renders.
tsx
'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:

tsx
<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.

tsx
'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?