React Integration
Integrate Document Stack PDF generation into React applications with hooks and components.
Architecture
React runs in the browser, so PDF generation must happen through your backend. The typical flow:
- React component calls your backend API
- Backend uses the Document Stack SDK to generate a PDF
- Backend returns the PDF URL to the React app
- React displays a download link or opens the PDF
Never Expose API Keys
Do not use the Document Stack SDK directly in React client code. Always generate PDFs from your backend.Custom Hook
Create a reusable hook for PDF generation:
hooks/useGeneratePdf.ts
import { useState } from "react";
interface GeneratePdfOptions {
templateId: string;
data: Record<string, unknown>;
}
export function useGeneratePdf() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
async function generate(options: GeneratePdfOptions) {
setLoading(true);
setError(null);
try {
const response = await fetch("/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error);
}
setPdfUrl(result.data.url);
return result.data.url;
} catch (err) {
const message = err instanceof Error ? err.message : "Generation failed";
setError(message);
return null;
} finally {
setLoading(false);
}
}
return { generate, loading, error, pdfUrl };
}Using the Hook
InvoiceGenerator.tsx
import { useGeneratePdf } from "./hooks/useGeneratePdf";
export function InvoiceGenerator() {
const { generate, loading, error, pdfUrl } = useGeneratePdf();
const handleClick = () => {
generate({
templateId: "tmpl_invoice",
data: {
customerName: "Acme Corp",
amount: 499.99,
date: new Date().toISOString(),
},
});
};
return (
<div>
<button onClick={handleClick} disabled={loading}>
{loading ? "Generating..." : "Generate Invoice"}
</button>
{error && <p style={{ color: "red" }}>{error}</p>}
{pdfUrl && (
<a href={pdfUrl} target="_blank" rel="noopener noreferrer">
📄 Download Invoice
</a>
)}
</div>
);
}With React Query
For more robust state management, use React Query (TanStack Query):
Using React Query
import { useMutation } from "@tanstack/react-query";
function useGeneratePdfMutation() {
return useMutation({
mutationFn: async (options: {
templateId: string;
data: Record<string, unknown>;
}) => {
const res = await fetch("/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
});
const result = await res.json();
if (!result.success) throw new Error(result.error);
return result.data;
},
});
}
// In your component
function InvoiceButton() {
const mutation = useGeneratePdfMutation();
return (
<button
onClick={() =>
mutation.mutate({
templateId: "tmpl_invoice",
data: { customerName: "Alice" },
})
}
disabled={mutation.isPending}
>
{mutation.isPending ? "Generating..." : "Generate"}
</button>
);
}In-Browser PDF Preview
Display the generated PDF inline using an iframe:
PDF Viewer Component
function PdfViewer({ url }: { url: string }) {
return (
<iframe
src={url}
width="100%"
height="600px"
style={{ border: "1px solid #e5e7eb", borderRadius: "8px" }}
title="Generated PDF"
/>
);
}Next Steps
- Next.js Integration — Server-side generation with Next.js
- Express Integration — Build the backend API
- Node.js SDK — SDK reference