Integrate with React
Build a document generation feature in your React app with the Rynko SDK.
Prerequisites
- A React project (Create React App, Vite, etc.)
- A backend proxy or API route to keep your API key secret
- A Rynko account with an API key
warning
Never use your API key directly in client-side code. Always call Rynko from a backend server or API route and expose a safe endpoint to your React app.
Step 1: Create a Backend Endpoint
Your React app needs a server-side endpoint that calls the Rynko API. Here is a minimal Express example:
npm install @rynko/sdk express cors dotenv
server.js
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { Rynko } from '@rynko/sdk';
const app = express();
app.use(cors());
app.use(express.json());
const rynko = new Rynko({ apiKey: process.env.RYNKO_API_KEY });
app.post('/api/generate-invoice', async (req, res) => {
try {
const job = await rynko.documents.generatePdf({
templateId: 'YOUR_TEMPLATE_ID',
variables: req.body,
});
const completed = await rynko.documents.waitForCompletion(job.jobId);
res.json({ downloadUrl: completed.downloadUrl });
} catch (error) {
res.status(500).json({ error: 'Generation failed' });
}
});
app.listen(4000);
Step 2: Build the React Component
Create a component with a form that submits data to your backend and displays the download link:
InvoiceGenerator.tsx
import { useState } from 'react';
export function InvoiceGenerator() {
const [customerName, setCustomerName] = useState('');
const [amount, setAmount] = useState('');
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
setDownloadUrl(null);
try {
const response = await fetch('/api/generate-invoice', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customerName,
invoiceNumber: `INV-${Date.now()}`,
amount: parseFloat(amount),
}),
});
if (!response.ok) throw new Error('Generation failed');
const data = await response.json();
setDownloadUrl(data.downloadUrl);
} catch (err) {
setError(err instanceof Error ? err.message : 'Something went wrong');
} finally {
setLoading(false);
}
}
return (
<div>
<h2>Generate Invoice</h2>
<form onSubmit={handleSubmit}>
<label>
Customer Name
<input
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
required
/>
</label>
<label>
Amount
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
required
/>
</label>
<button type="submit" disabled={loading}>
{loading ? 'Generating...' : 'Generate PDF'}
</button>
</form>
{error && <p style={{ color: 'red' }}>{error}</p>}
{downloadUrl && (
<a href={downloadUrl} target="_blank" rel="noopener noreferrer">
Download Invoice PDF
</a>
)}
</div>
);
}
Step 3: Use the Component
Render the component in your app:
App.tsx
import { InvoiceGenerator } from './InvoiceGenerator';
function App() {
return (
<div>
<h1>My App</h1>
<InvoiceGenerator />
</div>
);
}
export default App;
tip
For production apps, consider using a data-fetching library like React Query or SWR to handle loading states, caching, and error retries.
Next Steps
- Integrate with Next.js for a full-stack setup with API routes
- Batch generate documents for multiple records
- Generate Excel reports from templates