Make a PDF with React & Make.cm and avoid the pain of ongoing service management [Part 2/2]
Tags: react, pdf, javascript, tutorial
Published: 2021-03-25 07:19:06

If it's your first time here check out Part 1 of this series here.

{% link https://dev.to/jamesrplee/make-a-pdf-with-react-make-cm-and-avoid-the-pain-of-ongoing-service-management-part-1-2-2kef %}


---

In [Part 1](https://dev.to/jamesrplee/make-a-pdf-with-react-make-cm-and-avoid-the-pain-of-ongoing-service-management-part-1-2-2kef) we created our certificate template and imported it into Make. With that done we can focus on building our certificate generator app.

<br />

## 3. Creating our App

Okay refresher time. What are we making again?

A react app with:

- A form to capture the name and course
- A function to generate our certificate
- A preview of our PDF, once generated

![app-preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5fkasfzc8bq21sr7tbs.png)

**[We're making this](https://certificate.make.cm/)**

<br />

For our App structure we're building the following. Our styling just be handled with standard CSS.

```
/certificate-app
  /src
    /components
      /Form
        index.js
        styles.css
      /Header
        index.js
        styles.css
      /Preview
        index.js
        styles.css
    App.css
    App.js
    index.js
```

I'd suggest going ahead and creating these files, we'll loop back on them later.

<br />

### Prepping our App

For our App let's get started by installing the necessary dependencies and then spinning up our server.

```shell
$ yarn add axios react-pdf
```

```shell
$ yarn start
```

Our dependencies:

- **Axios**: will handle our POST request to Make
- **react-pdf**: will allow us to render the resulting PDF that Make sends us to the front end

Our `App.js` will be structured like this.

I've already setup a simple `useState` hook to capture the formData (so you don't need to!) that we'll hook up to our `<Form/>` component that we'll create in the next step.

```jsx
import { useState } from 'react';
import axios from 'axios';
import 'minireset.css';

import './App.css';

// import Header from './components/Header'
// import Form from './components/Form'
// import Preview from './components/Preview'

function App() {
  const [formData, setFormData] = useState({
    name: '',
    course: '',
  });

  return (
    <div className="App">
      <div className="container">
        {/* <Header /> */}

        <section>
          <div>
            {/* FORM */}
            <button type="button">Make my certificate</button>
          </div>
          <div>
            {/* PREVIEW */}
            {/* DOWNLOAD */}
          </div>
        </section>

        <footer>Built with React and Make.cm</footer>
      </div>
    </div>
  );
}

export default App;
```

Let's get some base styles out of the way, so in `App.css` remove what's in there and paste this in.

```css
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500&family=Poppins:wght@800&display=swap');

:root {
  --blue: #0379ff;
  --light-blue: #9ac9ff;
  --dark-blue: #0261cc;
  --white: #fff;
  --black: #101820;
  --blackAlpha: #10182010;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
    'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
    'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 16px;
}

.App {
  font-family: 'IBM Plex Sans';
}

.container {
  width: 100%;
  margin: 0 auto;
}

@media (min-width: 1024px) {
  .container {
    width: 1024px;
  }
}

section {
  width: 100%;
  display: grid;
  grid-template-columns: 2fr 1fr;
  padding-left: 8.5rem;
}

button {
  font-size: 1.25rem;
  background-color: var(--blue);
  border-radius: 6px;
  border: 0;
  padding: 1rem 2rem;
  font-weight: bold;
  color: var(--white);
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

footer {
  padding-top: 4rem;
}

.download {
  background-color: var(--dark-blue);
  color: white;
  font-size: 1.25rem;
  border-radius: 6px;
  border: 0;
  padding: 1rem 2rem;
  font-weight: bold;
  margin-top: 2rem;
  text-align: right;
  text-decoration: none;
}
```

While we're at it let's create the `<Header />` component. Go to your `components/Header/index.js` and paste the following

```jsx
import './styles.css';

const Header = () => (
  <header>
    <Icon />
    <h1>Certificate Maker</h1>
  </header>
);

const Icon = () => (
  <svg
    width="99"
    height="139"
    viewBox="0 0 99 139"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
  >
    <path d="M0 0H99V138.406L52.1955 118.324L0 138.406V0Z" fill="#0379FF" />
    <path
      d="M25.4912 83.2515C25.4912 79.4116 27.0222 75.7289 29.7474 73.0137C32.4727 70.2985 36.1689 68.7731 40.0229 68.7731C43.877 68.7731 47.5732 70.2985 50.2984 73.0137C53.0236 75.7289 54.5546 79.4116 54.5546 83.2515M40.0229 59.724C40.0229 55.8841 41.5539 52.2014 44.2791 49.4862C47.0044 46.7709 50.7006 45.2455 54.5546 45.2455C58.4087 45.2455 62.1049 46.7709 64.8301 49.4862C67.5553 52.2014 69.0863 55.8841 69.0863 59.724V83.2515"
      stroke="#fff"
      strokeWidth="10.6193"
    />
  </svg>
);

export default Header;
```

And then the same in `components/Header/styles.css`

```css
header {
  display: flex;
  justify-content: flex-start;
}

h1 {
  font-family: 'Poppins';
  color: var(--blue);
  padding: 2rem;
  font-size: 2.5rem;
}
```

Don't forget to uncomment the `import` and the component for your new `Header` in your `App.js`.

### Creating the form component

Our `<Form/>` component will capture the custom `name` and `course` inputs that will be sent to Make. We will use our `formData` and `setFormData` hook from `App.js` to set the initial state and handle any changes to that state.

Paste the following in your `src/components/Form/index.js` file.

```jsx
import './styles.css';

const Form = ({ formData, setFormData }) => {
  function handleChange(evt) {
    const value = evt.target.value;
    setFormData({
      ...formData,
      [evt.target.name]: value,
    });
  }

  return (
    <form>
      <div>
        <label htmlFor="name">Awarded to</label>
        <input
          type="text"
          id="name"
          name="name"
          placeholder={formData.name === '' && 'Name Surname'}
          value={formData.name}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="course">for completing</label>
        <input
          id="course"
          name="course"
          placeholder={
            formData.course === '' && 'Creating PDFs with React & Make.cm'
          }
          value={formData.course}
          onChange={handleChange}
        />
      </div>
    </form>
  );
};

export default Form;
```

It'll look pretty ugly so let's add some styles at `src/components/Form/styles.css`

```css
label {
  font-size: 1.2rem;
  display: block;
  margin-bottom: 1rem;
}

input {
  border: 0;
  padding: 0;
  display: block;
  width: 100%;
  font-size: 2rem;
  margin-bottom: 2rem;
  color: var(--blue);
}

input:focus {
  outline: none;
}

input::placeholder {
  color: var(--light-blue);
}

input:focus::placeholder,
input:active::placeholder {
  color: var(--blue);
}

input[name='name'] {
  font-family: 'Poppins';
  font-size: 3rem;
}

input[name='course'] {
  font-family: 'IBM Plex Sans';
  font-weight: 500;
  font-size: 2rem;
}
```

Finally lets uncomment the `import` and the component for your `Form` in your `App.js` and pass in `formData` and `setFormData` so we can move our state around.

```jsx
import { useState } from 'react';
import axios from 'axios';
import 'minireset.css';

import './App.css';

import Header from './components/Header';
import Form from './components/Form';
// import Preview from './components/Preview'

function App() {
  const [formData, setFormData] = useState({
    name: '',
    course: '',
  });

  return (
    <div className="App">
      <div className="container">
        <Header />

        <section>
          <div>
            <Form formData={formData} setFormData={setFormData} />
            <button type="button">Make my certificate</button>
          </div>
          <div>
            {/* Preview */}
            {/* Download */}
          </div>
        </section>

        <footer>Built with React and Make.cm</footer>
      </div>
    </div>
  );
}

export default App;
```

<br />

### Creating the request

Now that we've got our `<Form/>` working lets setup our request to Make. For this we'll do the following

- Create the onClick event
- Create our request
- Handle some state management
- Be able to do something with the generated certificate

On our `<button>` in `App.js` let's set an `onClick` event that triggers a function called `generateCertificate`.

```jsx
<button type="button" onClick={generateCertificate}>
  Make my certificate
</button>
```

For our `generateCertificate` function we can do the following.

We pass in the event (`e`) and prevent the default action.

```jsx
function generateCertificate(e) {
  e.preventDefault();
}
```

We need to then setup the various `const`'s for our request to Make.

For our request we will be performing a synchronous POST request.

The request can be handled synchronously because the template that we will be generating will resolve in under 30 sec.

If we were generating something that was computationally heavier (ie. a PDF booklet with a lot of images or generating a video from our template) we would need to use Make's async API. But in this case a sync request is fine.

#### URL

To find your API URL navigate to your imported certificate in Make and copy the `apiUrl` from the API playground.

![api playground in Make app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bn9ut56gm6t6ya2wwgpw.png)

<br />

The structure of our URL is as follows.

```url
https://api.make.cm/make/t/[template-id]/sync
```

- `make`: As we are calling the Make API
- `t`: To specify a template
- `[template-id]`: To specify the id of the template to generate
- `sync`: The request type to perform (ie. `sync` or `async`

```jsx
function generateCertificate(e) => {
  e.preventDefault();
  
  const url = [MAKE-API-URL]
}
```

#### Headers

We can then specify our `headers` for our request. In this case we just need to specify the `Content-Type` and our `X-MAKE-API-KEY`.

The Make API key can also be found from the API playground of your imported template (see in the above photo). If you want you can generate a new one.

```jsx
function generateCertificate(e) => {
  e.preventDefault();

  const url = [MAKE_API_URL];

  const headers = {
    'Content-Type': 'application/json',
    'X-MAKE-API-KEY': [MAKE_API_KEY],
  }
}
```

#### Data

Now let's specify the body of our request. In this case we want an A4 PDF certificate with the name and course that is encapsulated in our `formData` state, and then we add our date to the request as well.

The body structure for the Make API is split up into 4 areas that will be used to generate our certificate:

- **format** (required): The file type to be generated. In our case `pdf`.
- **size or customSize** (required): The width, height and unit that the final generated file will come out as. In this case `A4`
- **data**: A custom object of data that will be available for your template to consume via the custom window object `templateProps`. For our certificate we will be sending the following
  - name (from `formData`)
  - course (from `formData`)
  - date (calculated from today's date)
- **postProcessing**: A set of parameters to augment the asset, post generation. For our PDF we want to `optimize` it for our users.

```jsx
function generateCertificate(e) => {
  e.preventDefault();

  const url = [MAKE_API_URL];

  const headers = {
    'Content-Type': 'application/json',
    'X-MAKE-API-KEY': [MAKE_API_KEY],
  }

  const data = {
    size: 'A4',
    'format': 'pdf',
    'data': {
      ...formData,
      date: new Date().toDateString().split(' ').slice(1).join(' ')
    },
    'postProcessing': {
      optimize: true
    }
  }
}
```

With all of our `consts` ready we can create our POST request with `axios`.

```jsx
function generateCertificate(e) => {
  e.preventDefault();

  const url = [MAKE_API_URL];

  const headers = {
    'Content-Type': 'application/json',
    'X-MAKE-API-KEY': [MAKE_API_KEY],
  }

	const data = {
    size: 'A4',
    'format': 'pdf',
    'data': {
      ...formData,
      date: new Date().toDateString().split(' ').slice(1).join(' ')
    },
    'postProcessing': {
      optimize: true
    }
  }

  axios.post(url, data, {
    headers: headers
  })
  .then((response) => {
    console.log(response)
  }, (error) => {
    console.log(error);
  });
}
```

Test out the event by clicking the button.

Give it a second to generate and check your console and you should have a result like this. Your newly made PDF is the `resultUrl` in the `data` object.

```jsx
{
    "data": {
        "resultUrl": "https://exports.make.cm/d012845b-b116-4468-ab00-e2c79b006e21.pdf?AWSAccessKeyId=ASIATSPIFSU4EQL7GW6O&Expires=1615921029&Signature=pf3X%2FYOAjWKXtkfnG49U%2BjGVwxI%3D&x-amz-security-token=IQoJb3JpZ2luX2VjENf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLXNvdXRoZWFzdC0yIkgwRgIhAK98rku7U6iKoY3TJ9xUJZGh9%2ByL%2By99JT96sCoP8ZZzAiEAvMdU%2F%2FNTCSygV28zNx4m5xe4UgHxbFyC%2BWKDKt92YLAq0QEIEBAAGgwyNDU4MzY5MTE5MjgiDK5SSXVBnx5YHlpkQCquAcdfUJX7cnCvxHwTCPzJLeJZB1Yg5x5nsjHI9DC63TJ5LXbaDLWbMllosnBMJ3u0%2BjUNuvvxkIt%2Bw5mY%2FNrYytY0%2BXVjukcbZO%2BZ0gx8kaTtVRJBrKP5TCwDHZu20%2FpKckR8muPL3OuNewH5g1BEkCqls6w72qdz7aaxEsvGwV5wzeVLJdotgQy6LQ%2FlcsyLqG7RiGyZouahjvnijpbIRYtfeTI5qXPCLtUl0SyfaDC8rcGCBjrfAXZicx8A6iCEhLBQwF8LtgPqgBQlTcwfapNQQ1gnUwlSnCBm6Lsm0kpsFnqHT0ockINp2STRJkkovS7lkKgOIP49ApSk9MRYJFy%2F8%2BfDeYToQ9K3y0aS2qY7HHigQwAX1dgjmWpL27aZEXriG%2F2uxcjEXwKzWySFNkQjlzVuTVHA3rucrMnZfuP3fPH82A10nce%2BTNx%2BLXKZgZz8rv50J3eQwLBVcq3phIGmnY%2B5meivIAqOCL1iYrMRqTZfNLdAxOqWdlMiGinYKGUZufsdpfr0xuq73unvmQ3MuDfDCDA%3D",
        "requestId": "d012845b-b116-4468-ab00-e2c79b006e21"
    },
    "status": 200,
    "statusText": "",
    "headers": {
        "content-length": "1055",
        "content-type": "text/plain; charset=utf-8"
    },
    "config": {
        "url": "https://api.make.cm/make/t/c43e9d1a-f0aa-4bf7-bf73-6be3084187d8/sync",
        "method": "post",
        "data": "{\"size\":\"A4\",\"format\":\"pdf\",\"data\":{\"name\":\"Name Surname\",\"course\":\"Creating things\",\"date\":\"Mar 16 2021\"}}",
        "headers": {
            "Accept": "application/json, text/plain, */*",
            "Content-Type": "application/json",
            "X-MAKE-API-KEY": "47bad936bfb6bb3bd9b94ae344132f8afdfff44c"
        },
        "transformRequest": [
            null
        ],
        "transformResponse": [
            null
        ],
        "timeout": 0,
        "xsrfCookieName": "XSRF-TOKEN",
        "xsrfHeaderName": "X-XSRF-TOKEN",
        "maxContentLength": -1,
        "maxBodyLength": -1
    },
    "request": {}
}
```

**Congrats! You just performed your first request outside of Make! 🎉**

There is a bit of a lag between clicking the button and getting a result so let's set up some really simple state management so we give our users at least some feedback.

Let's set up a simple loading state for when we send our request.

In `App.js` create the following `useState` hook caleed `isLoading`.

In our `generateCertificate` function we'll set `isLoading` to `true` when our function fires and then `false` when our request finishes (or our request errors for whatever reason).

```jsx
const [formData, setFormData] = useState({
  name: '',
  course: '',
});
const [isLoading, setIsLoading] = useState(false)

const generateCertificate = (e) => {
  e.preventDefault();

  setIsLoading(true)

	...

  axios.post(url, data, {
    headers: headers
  })
  .then((response) => {
    console.log(response);
    setIsLoading(false)
  }, (error) => {
    console.log(error);
    setIsLoading(false)
  });
}
```

We'll update the button in our `return` so it disables when `isLoading` is `true`.

```jsx
<button type="button" disabled={isLoading} onClick={generateCertificate}>
  {isLoading ? 'Making...' : 'Make my certificate'}
</button>
```

Console logging is great but let's actually put that certificate somewhere.

We can create another hook called `certificate` to capture our result.

```jsx
// App.js

const [formData, setFormData] = useState({
  name: '',
  course: '',
});
const [isLoading, setIsLoading] = useState(false)
const [certificate, setCertificate] = useState(null)

const generateCertificate = (e) => {
  ...

  axios.post(url, data, {
    headers: headers
  })
  .then((response) => {
    setIsLoading(false)
    setCertificate(response.data.resultUrl)
  }, (error) => {
    console.log(error);
    setIsLoading(false)
  });
}
```

Finally let's create a simple `Download` button for when the result is available.

```jsx
<div className="App">
  <div className="container">
    <Header />

    <section>
      <div>
        <Form formData={formData} setFormData={setFormData} />
        <button
          type="button"
          disabled={isLoading}
          onClick={generateCertificate}
        >
          {isLoading ? 'Making...' : 'Make my certificate'}
        </button>
      </div>
      <div>
        {/* Preview (optional) */}
        {certificate && (
          <a
            className="download"
            target="_blank"
            rel="noreferrer"
            href={certificate}
          >
            Download
          </a>
        )}
      </div>
    </section>

    <footer>Built with React and Make.cm</footer>
  </div>
</div>
```

**Isn't it a thing of beauty! 🥰**

### Creating the preview component (optional)

This step is completely optional but I think it rounds out the whole application. We're going to use `react-pdf` to create a preview of our certificate once it has generated.

We should've installed `react-pdf` at the start, but if you haven't yet you can just run this in your terminal.

```
yarn add react-pdf
```

For our `<Preview/>` component we're going to be passing the `certificate` and `isLoading` props into our component and when the certificate has been generated `react-pdf` will create a preview of that.

Paste the following into `components/Preview/index.js`.

```jsx
import { Document, Page, pdfjs } from 'react-pdf';
import './styles.css';

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;

const Preview = ({ certificate, isLoading }) => {
  return (
    <div className="pdf">
      {!certificate && (
        <div className="loader">
          {isLoading ? 'Making...' : 'Make one and see!'}
        </div>
      )}
      {certificate && (
        <Document file={certificate} loading="Loading...">
          <Page pageNumber={1} />
        </Document>
      )}
    </div>
  );
};

export default Preview;
```

For our styles in `components/Preview/styles.css`

```css
.pdf {
  border: 0.25rem solid var(--black);
  border-radius: 1rem;
  box-shadow: 1rem 1rem 0 var(--blackAlpha);
  padding-bottom: 137.3%;
  position: relative;
  overflow: hidden;
  margin-bottom: 3rem;
}

.pdf div {
  position: absolute;
  font-weight: 500;
}

.pdf .loader {
  padding: 1.5rem;
}

.react-pdf__Page__canvas {
  width: 100% !important;
  height: initial !important;
}
```

And then in the `App.js` we can import it and pass the props down.

```jsx
import { useState } from 'react';
import axios from 'axios';
import 'minireset.css';

import './App.css';

import Header from './components/Header'
import Form from './components/Form'
import Preview from './components/Preview'

function App() {
  ...

  return (
    <div className="App">
      <div className="container">
        <Header />

        <section>
          <div>
            <Form formData={formData} setFormData={setFormData} />
            <button type="button">Make my certificate</button>
          </div>
          <div>
            <Preview certificate={certificate} isLoading={isLoading} />
            {certificate && (
              <a
                className="download"
                target="_blank"
                rel="noreferrer"
                href={certificate}
              >
                Download
              </a>
            )}
          </div>
        </section>

        <footer>
          Built with React and Make.cm
        </footer>
      </div>
    </div>
  );
}

export default App;
```

### Cleaning it up

The only thing left to do at this stage is secure my Make key and API URL.

For this we can use `dotenv` just so we're not committing keys into Github and beyond. While it won't stop people from being able to see this info on the client I think it just keeps the surface area a lot smaller.

```jsx
yarn add dotenv
```

Add a file on the root called `.env.development`

```jsx
REACT_APP_MAKE_KEY = [YOUR_MAKE_KEY];
REACT_APP_MAKE_URL = [YOUR_MAKE_URL];
```

And then in your `App.js` you can point to your environment variables like so

```jsx
const url = process.env.REACT_APP_MAKE_URL;

const headers = {
  'Content-Type': 'application/json',
  'X-MAKE-API-KEY': process.env.REACT_APP_MAKE_KEY,
};
```

If you make any changes to your `.env` files remember to restart your local server.

### And, that's it! 🙌

Thank you so much for following on with the first of many guides about how to use [Make.cm](http://make.cm) and get the most out of the API.

I know it was a long one, but I didn't want to give you some click baity title about **_CREATING A PDF IN UNDER 5 MIN_**. If you missed it in [Part 1](/blog/make-a-pdf) here are some links to the resources that I used to Make this application.

{% github makecm/certificate-app no-readme %}

{% github makecm/certificate-template no-readme %}

If you have any questions or issues along the way let me know at [@jamesrplee](https://twitter.com/jamesrplee) on Twitter and I'll be happy to help you out.

<p style="text-align: center">Thank you so much and happy Making,</p>

<p style="text-align: center">James</p>