Skip to content

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

Screenshot op set-form-data

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.
    shell
    unzip react-set-form-data.zip -d react-set-form-data
  • Open a terminal and go to that directory
    shell
    cd react-set-form-data
  • Install dependencies
    shell
    npm install
    shell
    yarn
  • Start the project
    shell
    npm run start
    shell
    yarn start