Appearance
Set form data - React
In this example we set form data in a PDF form. You can setup one-way or two-way data bindings between the form fields in the PDF document and input elements in html.
Prerequisites
This example assumes that there exists a document form.pdf
in your storage. If you do not have such a document you can run the following command to add a document with fields under that name:
shell
node src/post-document.js
Reading the form fields and form field definitions
In the handler for the loaded event you can retrieve the field definitions and form data.
ts
const onLoaded = async ({
target,
}: PdfDocumentCustomEvent<string | null>) => {
setFormData(await target.getFormData());
setFieldDefinitions(await target.getFields());
};
When you want to show an input element in your html for a PDF field you can retrieve the field type from the field definition:
ts
const fieldType = fieldDefinition?.fieldType;
Depending on the field type there are other fields available in the field definition such as checkBoxOnValue
, checkBoxOffValue
, listOptions
and radioButtonOptions
ts
const radioButtonOptions = fieldDefinition?.radioButtonOptions;
const listOptions = fieldDefinition?.listOptions;
const checkboxOnValue = fieldDefinition?.checkBoxOnValue || "On";
const checkboxOffValue = fieldDefinition?.checkBoxOffValue || "Off";
const checked = value === checkboxOnValue;
By combining this information you can dynamically determine which type of HTML input to use.
html
const RadioButtonInput = () =>
radioButtonOptions?.map((option) => (
<Fragment key={option}>
<input
key={option}
type="radio"
value={option}
checked={option === value}
onChange={(event) =>
onChange((event.target as HTMLInputElement).value)
}
/>{" "}
{option}
</Fragment>
));
const TextFieldInput = () => (
<input
type="text"
value={value}
onInput={(event) => onChange((event.target as HTMLInputElement).value)}
/>
);
const ListFieldInput = () => (
<select
value={value}
name={name}
size={fieldType == "ListBoxField" ? listOptions?.length || 0 : undefined}
onChange={(event) => onChange((event.target as HTMLSelectElement).value)}
>
{listOptions?.map((option) => (
<option key={option.export} value={option.export}>
{option.display}
</option>
))}
</select>
);
const CheckBoxInput = () => (
<input
type="checkbox"
value={value}
checked={checked}
onChange={onCheckBoxChanged}
/>
);
Screenshot
Code
tsx
import {
IPdfDocument,
IPdfField,
PdfApplication,
PdfDocument,
PdfDocumentCustomEvent,
PdfPages,
defineCustomElements,
} from "@tallcomponents/unopdf-react";
import { Fragment, useEffect, useRef, useState } from "react";
import PdfInput from "./PdfInput";
defineCustomElements();
// Note: this example uses Vite and retrieves the keys from the environment.
// This should not be used in production.
// See the authentication section in the usage guide for more information.
const publickey = import.meta.env.VITE_PUBLIC_KEY;
const privatekey = import.meta.env.VITE_PRIVATE_KEY;
function App() {
const [formDocument, setFormDocument] = useState<IPdfDocument>();
const pdfApplication = useRef<HTMLPdfApplicationElement | null>(null);
const pdfDocument = useRef<HTMLPdfDocumentElement | null>(null);
const [formData, setFormData] = useState<Record<string, string>>({});
const [fieldDefinitions, setFieldDefinitions] = useState<
IPdfField[] | null
>();
useEffect(() => {
function getFormDocument() {
if (pdfApplication.current) {
pdfApplication.current.getDocuments().then((documents) => {
const formDocument = documents.find(
(doc) => doc.originalFileName == "form.pdf",
);
if (formDocument) {
setFormDocument(formDocument);
}
});
}
}
getFormDocument();
}, [pdfApplication]);
useEffect(() => {
if (pdfDocument.current && formDocument && formDocument.id) {
pdfDocument.current.open(formDocument.id);
}
}, [pdfDocument, formDocument]);
// #region on-loaded
const onLoaded = async ({
target,
}: PdfDocumentCustomEvent<string | null>) => {
setFormData(await target.getFormData());
setFieldDefinitions(await target.getFields());
};
// #endregion on-loaded
const onFormDataChanged = async ({
target,
}: PdfDocumentCustomEvent<unknown>) => {
setFormData(await target.getFormData());
};
const onChanged = (value: Record<string, string>) => {
// store form data
const _pdf = pdfDocument.current as HTMLPdfDocumentElement;
_pdf.setFormData(value);
setFormData(value);
};
// #region save-document-handler
const saveDocument = async () => {
if (pdfDocument.current) {
const responseDocument = await pdfDocument.current.save()
if (responseDocument) {
console.log(`document saved with id ${responseDocument.id}`);
} else {
console.log(`document not saved`);
}
// In addition to saving the pdf, you can also access
// the form data at this point to submit it
// e.g. to a rest endpoint for processing.
// The pdf that is saved can be kept as part
// of the audit trail for the transaction.
console.log("form data", await pdfDocument.current.getFormData());
}
};
// #endregion save-document-handler
const downloadDocument = async () => {
await pdfDocument.current?.download();
};
return (
<>
<h1>UnoPdf Example: Fill Form Fields and Save with FormData in React</h1>
<PdfApplication
ref={pdfApplication}
publickey={publickey}
privatekey={privatekey}
/>
{
// #region pdf-document
}
<PdfDocument
ref={pdfDocument}
onLoaded={onLoaded}
onFormdatachanged={onFormDataChanged}
/>
{
// #endregion pdf-document
}
<div style={{ display: "flex" }}>
<div>
<PdfPages />
</div>
<div>
<div style={{ padding: "10px" }}>
You can fill out the fields in the document and see your changes
reflected below
</div>
<div style={{ padding: "10px" }}>
<h2>Fields</h2>
{Object.entries(formData).map(([name, value]) => (
<Fragment key={name}>
<div>
{name}: {value}
</div>
<PdfInput
formData={formData}
fieldDefinitions={fieldDefinitions!}
name={name}
change={onChanged}
/>
</Fragment>
))}
</div>
<div style={{ display: "flex" }}>
<div style={{ padding: "10px" }}>
{/*
<!-- #region save-document-button -->
*/}
<button
type="button"
style={{ fontSize: "20px" }}
onClick={saveDocument}
>
save
</button>
{/*
<!-- #endregion save-document-button -->
*/}
</div>
<div style={{ padding: "10px" }}>
<button
type="button"
style={{ fontSize: "20px" }}
onClick={downloadDocument}
>
download
</button>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
tsx
import { IPdfField } from "@tallcomponents/unopdf-core";
import { Fragment, useState } from "react";
interface Props {
formData: Record<string, string>;
fieldDefinitions: IPdfField[];
name: string;
change: (value: Record<string, string>) => void;
}
function PdfInput({ formData, fieldDefinitions, name, change }: Props) {
const [value, setValue] = useState(formData[name]);
const onChange = (value: string) => {
const f = { ...formData, [name]: value };
change(f);
setValue(value);
};
const fieldDefinition = fieldDefinitions.find((f) => f.fullName === name);
// #region field-type
const fieldType = fieldDefinition?.fieldType;
// #endregion field-type
// #region read-field-options
const radioButtonOptions = fieldDefinition?.radioButtonOptions;
const listOptions = fieldDefinition?.listOptions;
const checkboxOnValue = fieldDefinition?.checkBoxOnValue || "On";
const checkboxOffValue = fieldDefinition?.checkBoxOffValue || "Off";
const checked = value === checkboxOnValue;
// #endregion read-field-options
const onCheckBoxChanged = () => {
if (formData[name] === checkboxOnValue) {
onChange(checkboxOffValue);
} else {
onChange(checkboxOnValue);
}
};
// #region input-field
const RadioButtonInput = () =>
radioButtonOptions?.map((option) => (
<Fragment key={option}>
<input
key={option}
type="radio"
value={option}
checked={option === value}
onChange={(event) =>
onChange((event.target as HTMLInputElement).value)
}
/>{" "}
{option}
</Fragment>
));
const TextFieldInput = () => (
<input
type="text"
value={value}
onInput={(event) => onChange((event.target as HTMLInputElement).value)}
/>
);
const ListFieldInput = () => (
<select
value={value}
name={name}
size={fieldType == "ListBoxField" ? listOptions?.length || 0 : undefined}
onChange={(event) => onChange((event.target as HTMLSelectElement).value)}
>
{listOptions?.map((option) => (
<option key={option.export} value={option.export}>
{option.display}
</option>
))}
</select>
);
const CheckBoxInput = () => (
<input
type="checkbox"
value={value}
checked={checked}
onChange={onCheckBoxChanged}
/>
);
// #endregion input-field
const Input = () => {
switch (fieldType) {
case "RadioButtonField":
return <RadioButtonInput />;
case "TextField":
return <TextFieldInput />;
case "ListBoxField":
case "DropDownListField":
return <ListFieldInput />;
case "CheckBoxField":
return <CheckBoxInput />;
default:
return `Unsupported Field Type: ${fieldType}`;
}
};
return (
<>
<Input />
</>
);
}
export default PdfInput;
Running the example
- Download the react - Set form data project
- Unzip the file to a directory
react-set-form-data
.shellunzip react-set-form-data.zip -d react-set-form-data
- Open a terminal and go to that directoryshell
cd react-set-form-data
- Install dependenciesshell
npm install
shellyarn
- Start the projectshell
npm run start
shellyarn start