I have a job application form where users upload their resumes, handled by react-hook-form. I can get the resume saved in state just fine, and I can send it as part of a request to the sendgrid API, but the attachment won't open when you receive it via email.
I've been at this for so long, I have no idea what I'm doing wrong. Any advice is much appreciated. Here's my code so far:
// components/Careers_Application_Form.tsx
import React, { useState } from 'react'
import { useForm } from 'react-hook-form'
export default function Careers_Application_Form() {
const [file, setFile] = useState(null)
const [fileName, setFileName] = useState('')
function handleUploadFile(selectedFile) {
if (selectedFile.length > 0) {
let fileToLoad = selectedFile[0]
setFileName(fileToLoad.name)
let fileReader = new FileReader()
fileReader.onload = function (fileLoadedEvent) {
let file = fileLoadedEvent.target.result
setFile(file.toString('base64'))
}
fileReader.readAsDataURL(fileToLoad)
}
}
const {
register,
watch,
handleSubmit,
formState: { errors },
} = useForm({})
// Handle the submit
const onSubmit = (data) => {
handleUploadFile(data.resume)
SendToSendgrid(data)
setSubmitted(true)
console.log(errors)
}
// SENDGRID
async function SendToSendgrid(data) {
data.file = file
data.fileName = fileName
await fetch('/api/sendJobApplication', {
method: 'POST',
body: JSON.stringify(data),
}).then((res) => res.json())
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type='file'
onChange={setFile}
placeholder='Upload Your Resume'
{...register('resume', {
required: true,
validate: {
lessThan10MB: (files) => files[0]?.size < 10000000 || 'Max 10MB',
acceptedFormats: (files) => files[0].type === 'application/pdf',
},
})}
/>
{errors.resume?.type === 'lessThan10MB' && (
<p>Error: Must Be Under 10MB</p>
)}
{errors.resume?.type === 'acceptedFormats' && (
<p>Error: Must Be A PDF</p>
)}
<input
type='submit'
value={'off she goes!'}
/>
</form>
)
}
Here's the sendgrid API file:
// pages/api/sendJobAppication.ts
import mail from "@sendgrid/mail";
mail.setApiKey(process.env.SENDGRID_API_KEY);
export default async function sendOnboarding(req, res) {
const body = JSON.parse(req.body);
await mail.send({
to: `redacted`,
from: {
email: "redacted",
name: "Pixel Bakery Robot",
},
subject: `Job Application: ${body.first_name} ${body.last_name} – ${body.position}`,
templateId: "redacted",
dynamicTemplateData: {
// ...more
},
attachments: [
{
content: `${body.file}`,
filename: `${body.fileName}`,
type: "application/pdf",
disposition: "attachment",
},
],
});
console.log(res.status(200).json({ status: "Ok" }));
}
Here's what happens when I try to open the attachment:
One issue is that you're mixing synchronous and asynchronous logic. Remove the two useState()
calls. When you set their values, you won't get the updated values until after the next render. So when handleUploadFile
returns and you enter SendToSendgrid
the file and fileName values are probably incorrect. I'd have handleUploadFile just return a tuple of [file, filename]
. Then on the server-side - you may be passing base64 to SendGrid. They may be expecting binary...
Check things are multiple points... Some tips that may help you debug:
EDIT: It looks like SendGrid does expect base64.
You can just use the variable directly without pulling them out of a string template:
attachments: [
{
content: `${body.file}`,
filename: `${body.fileName}`,
type: "application/pdf",
disposition: "attachment",
},
],
// change to
attachments: [
{
content: body.file,
filename: body.fileName,
type: "application/pdf",
disposition: "attachment",
},
],