React

How to getting started with Joyfill in a React application

Overview

We currently support any React and JS. However, Joyfill Embedded or Joyfill Native can be interwoven to make up various combinations of solutions to fit your product's needs. We support really any JS setup whether you are using Angular, NextJS, ASP.Net for web, or even mobile JS hybrids like React-Native and Ionic.

Joyfill Platform solutions can all be used with just about any workflow you need.

Joyfill Platform Solutions

In this guide we will be using the following Joyfill Platform Solutions

Guide

userAccessTokens & identifiers will need to be stored on your end (usually on a user and set of existing form field-based data) in order to interact with our API and UI Components effectively

Project Requirements

  • React and React DOM v18+

Install Dependency

npm install @joyfill/components
yarn add @joyfill/components

Implement your code

Below is a usable example of our react functionality.

Make sure to replace the userAccessToken and documentId. Note that documentID is just for this example, you can call our List all documents endpoint and grab an ID from there.

import React, { useState, useEffect } from 'react';
import './App.css';
import Loading from "./Loading";

// 1. import the JoyDoc component
import { JoyDoc } from '@joyfill/components';

import {
  joyfillSave,
  joyfillGenerate,
  joyfillRetrieve,
} from './api.js';

// 2, replace here with steps from our Getting Started -> Setup guide
const userAccessToken = '<replace_me>';
const documentId = '<replace_me>';

function App() {
  const [ doc, setDoc ] = useState(null);
  const [ mode, setMode ] = useState('edit');
  const [pdfLink, setPdfLink] = useState(null);
  const [loading, setLoading] = useState(null);

  // retrieve the document from our api (you can also pass an initial documentId into JoyDoc)
  useEffect(() => {
    retrieveJofillDocument();
  }, []);

  const retrieveJofillDocument = async () => {
    setLoading('retrieving document...');
    const response = await joyfillRetrieve(documentId, userAccessToken);
    setDoc(response);
    setLoading(null);
  }

  // save the form and generate a pdf as an example
  const saveForm = async (doc) => {
    setLoading('saving document & generating pdf...');
    await joyfillSave(doc, userAccessToken);
    const downloadableLink = await joyfillGenerate(doc.identifier, userAccessToken);
    setPdfLink(downloadableLink);
    setLoading(null);
  }

  return (
    <div className="App">
      <div className="row">
        <column>
          <a
            className="App-link"
            href="https://docs.joyfill.io/"
            target="_blank"
          >
            Checkout Joyfill Docs for More
          </a>
        </column>

        <column>
          <img src={'https://joyfill.io/wp-content/uploads/2022/03/Joyfill-logo-website-mobile.svg'} className="App-logo" alt="logo" />
        </column>

        <column>
          <button onClick={() => setMode(mode === 'edit' ? 'fill' : 'edit')}>
            Toggle Form Mode
          </button>
          <button onClick={() => saveForm(doc)}>
            Save Changes
          </button>
        </column>
      </div>

      <Loading text={loading} />
      {pdfLink && <a href={pdfLink} download>Download PDF</a>}

      <div className='form'>
        
        {/* 3. implement the JoyDoc in your react project */}
        <JoyDoc
          mode={mode}
          doc={doc}
          onChange={(params, changes, doc) => {
            console.log('onChange doc: ', doc);
            setDoc(doc);
          }}
          onUploadAsync={async ({ documentId }, fileUploads) => {
            // to see a full utilization of upload see api.js -> examples
            console.log('onUploadAsync: ', fileUploads);
          }}
        />
      </div>
    </div>
  );
}

export default App;


// 2. setup helpers
export const joyfillGenerate = async (identifier, userAccessToken) => {

  const response = await fetch("https://api-joy.joyfill.io/v1/documents/exports/pdf", {
    method: 'POST',
    mode:'cors',
    headers: {
      Authorization: `Bearer ${userAccessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ document: identifier })
  });
  

  const data = await response.json();
  return data.download_url;

}


export const joyfillRetrieve = async (documentIdentifier, userAccessToken) => {

  const response = await fetch(`https://api-joy.joyfill.io/v1/documents/${documentIdentifier}`, {
    method: 'GET',
    mode: 'cors',
    headers: {
      Authorization: `Bearer ${userAccessToken}`,
      'Content-Type': 'application/json'
    },
  });

  const data = await response.json();
  return data;
}


export const joyfillSave = async (doc, userAccessToken) => {

  const response = await fetch(`https://api-joy.joyfill.io/v1/documents/${doc.identifier}`, {
    method: 'POST',
    mode:'cors',
    headers: {
      Authorization: `Bearer ${userAccessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type: "document",
      stage: "published",
      name: "ACME_WorkOrder",
      files: [doc.files[0]]
    })
  });

  const data = await response.json();
  return data;

};


export const getDataUriForFileUpload = async (fileUpload) => {

  return new Promise((ful, rej) => {

    const reader = new FileReader();

    reader.readAsDataURL(fileUpload);
    
    reader.onloadend = async () => {
      ful(reader.result);
    };

  });

};

export const uploadFileAsync = async (documentId, dataUri, userAccessToken) => {

  const response = await fetch(`https://api-joy.joyfill.io/v1/documents/${documentId}/files/datauri`, {
    method: 'POST', 
    mode: 'no-cors',
    headers: {
      Authorization: `Bearer ${userAccessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ file: dataUri })
  });

  const data = await response.json();
  return data;

};

export const convertPDFPagesToBase64Async = async (dataUri, userAccessToken) => {

  try {

    const response = await fetch(`https://api-joy.joyfill.io/v1/utilities/pdf/to/png/datauri`, {
      method: 'POST', 
      mode: 'no-cors',
      headers: {
        Authorization: `Bearer ${userAccessToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ file: dataUri })
    });

    return await response.json();

  } catch (error) {

    throw error;

  }

};

import React from "react";

export default function LoadingSpinner({ text }) {
  return text ? (
    <>
      <div className="spinner-container">
        <div className="loading-spinner"></div>
      </div>
      <p>{text}...</p>
    </>
  ) : null;
}

.App {
  text-align: center;
}

.App-logo {
  height: 7vmin;
  pointer-events: none;
  margin-top: 5px;
  margin-left: -65px;
}

.App-body {
  background-color: #282c34;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}


.App-link {
  color: #0870f0;
}

.form {
  border: 2px solid rgb(235, 235, 235);
  margin: 15px;
  padding: 15px;
  border-radius: 10px;
}

.form a {
  padding: 10px;
}

.row {
  display: flex;
  flex-direction: row;
  flex: 1;
  justify-content: space-between;
  align-items: center;
  background-color: rgb(235, 235, 235);
  padding: 5px 0;
  margin: 10px;
  border-radius: 10px;
}

.row a {
  padding: 0 30px;
}

button {
  padding: 10px;
  border-radius: 8px;
  background-color: #0897f0;
  color: white;
  border: 0;
  margin-right: 25px;
  cursor: pointer;
}

.column {
  flex-direction: column;
}

@keyframes spinner {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.loading-spinner {
  width: 10px;
  height: 10px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #383636;
  border-radius: 70%;
  animation: spinner 1.5s linear infinite;
}

.spinner-container {
  display: grid;
  justify-content: center;
  align-items: center;
  margin-top: 15px;
}


What’s Next