Skip to main content

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