@dispatchcms/react
v0.0.6
Published
React hooks and provider for Dispatch CMS
Maintainers
Readme
@dispatchcms/react
React hooks and provider for Dispatch CMS. Use DispatchProvider and usePost / usePosts to fetch published posts in any React app (Next.js, Vite, etc.).
Install
npm install @dispatchcms/react
# or
pnpm add @dispatchcms/react
# or
yarn add @dispatchcms/reactSetup
Wrap your app with DispatchProvider and pass your site key (from Dispatch → Sites → your site). Read the key from your app’s config or environment (e.g. in Vite use import.meta.env.VITE_DISPATCH_SITE_KEY, or pass a literal in development):
import { DispatchProvider } from "@dispatchcms/react";
export default function App({ children }) {
return (
<DispatchProvider siteKey={import.meta.env.VITE_DISPATCH_SITE_KEY}>
{children}
</DispatchProvider>
);
}To use a different API base URL, pass the apiBase prop to the provider.
API
usePost(slug)
Fetches a single published post by slug.
- Returns:
{ post: Post | null, isLoading: boolean, error?: string } - On failure or missing site key, does not throw; returns
post: null,isLoading: false, anderrorset.
usePosts()
Fetches all published posts for the site.
- Returns:
{ posts: Post[], isLoading: boolean, error?: string } - On failure or missing site key, does not throw; returns
posts: [],isLoading: false, anderrorset.
Graceful failure
- If the API request fails, hooks return
post: null/posts: [],isLoading: false, anderror: "Failed to load". The React tree is never crashed. - If no
siteKeyis provided to the provider,console.warnis logged and hooks return the same safe empty/error state witherror: "Missing site key".
Types
Post– Post from the CMS. Fields includeid,title,slug,content,excerpt,featured_image,published,published_at,created_at,updated_at,site_id. Thecontentfield is TipTap (ProseMirror) JSON.UsePostResult/UsePostsResult– Return types of the hooks.
Rendering content
Post content is stored as TipTap (ProseMirror) JSON. Render it with TipTap’s static renderer so formatting, links, and images match the CMS:
- React: Use
renderToReactElementfrom@tiptap/static-renderer/pm/reactwith the same extensions as the CMS (e.g.StarterKit, or Document, Paragraph, Heading, Bold, Italic, Blockquote, BulletList, ListItem, Link, Image). NodangerouslySetInnerHTMLneeded. - HTML string: Use
renderToHTMLStringfrom@tiptap/static-renderer/pm/html-string(e.g. for SSR or non-React output).
For full parity with the CMS editor, use the same extensions (Document, Paragraph, Text, Heading, Bold, Italic, Blockquote, BulletList, ListItem, Link, Image). See TipTap static renderer docs.
import { renderToReactElement } from "@tiptap/static-renderer/pm/react";
import StarterKit from "@tiptap/starter-kit";
function TipTapContent({ content }: { content: unknown }) {
if (content == null || typeof content !== "object") return null;
return renderToReactElement({
extensions: [StarterKit],
content: content as { type: string; content?: unknown[] },
});
}
// In your post page:
<TipTapContent content={post.content} />Example
// List posts (e.g. homepage)
import { usePosts } from "@dispatchcms/react";
export default function HomePage() {
const { posts, isLoading, error } = usePosts();
if (isLoading) return <p>Loading…</p>;
if (error) return <p>{error}</p>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
);
}// Single post page (e.g. /blog/:slug)
import { usePost } from "@dispatchcms/react";
import { TipTapContent } from "./TipTapContent"; // or inline renderToReactElement
export default function PostPage({ slug }) {
const { post, isLoading, error } = usePost(slug);
if (isLoading) return <p>Loading…</p>;
if (error || !post) return <p>{error ?? "Not found"}</p>;
return (
<article>
<h1>{post.title}</h1>
{post.excerpt && <p>{post.excerpt}</p>}
<TipTapContent content={post.content} />
</article>
);
}License
MIT
