Last active
July 26, 2022 16:39
-
-
Save jdretz/2c76eadcd0ea46fb0aa63a25e115f4b1 to your computer and use it in GitHub Desktop.
Gatsby Function, Sendgrid, ReactJS, Typescript, Formik, Material-UI, and useReducer. These files are from a ReactJS form built with Formik that sends data to Gatsby Function that triggers email via Sendgrid API. The workflow is built with Typescript and uses a custom useReducer hook to bundle state updates.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { GatsbyFunctionRequest, GatsbyFunctionResponse } from 'gatsby' | |
import sendgrid from '@sendgrid/mail' | |
type ContactBody = { | |
message: string | |
email: string | |
name: string | |
} | |
export default function handler( | |
req: GatsbyFunctionRequest<ContactBody>, | |
res: GatsbyFunctionResponse | |
) { | |
if (req.method !== `POST`) { | |
return res.status(405).send('') | |
} | |
try { | |
// Your API Key from Sendgrid | |
if (!process.env.SENDGRID_API_KEY) { | |
return res.status(500).json({ | |
message: `Failed to set up server environment`, | |
}) | |
} | |
sendgrid.setApiKey(process.env.SENDGRID_API_KEY) | |
if (req.body) { | |
return sendgrid | |
.send({ | |
from: req.body.email, | |
to: `XXX`, | |
subject: `Message on RETZDEV from ${req.body.name}`, | |
text: req.body.message, | |
html: `<p>${req.body.message}</p>`, | |
}) | |
.then(() => { | |
res.status(200).send('') | |
}) | |
} | |
return res.status(400).send('') | |
} catch (err) { | |
return res.status(500).json({ message: 'There was an error', error: err }) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { EmailOutlined } from '@mui/icons-material' | |
import { Box, Button, Typography } from '@mui/material' | |
import axios from 'axios' | |
import { Field, FieldProps, Form, Formik } from 'formik' | |
import React from 'react' | |
import * as Yup from 'yup' | |
import useHttpReducer from '../../hooks/useHttpReducer' | |
import useMediaQueries from '../../hooks/useMediaQueries' | |
import RDTextField from '../RDTextField/RDTextField' | |
type FormValues = { | |
name: string | |
email: string | |
message: string | |
} | |
const ContactForm: React.FC = () => { | |
const initVals: FormValues = { | |
name: '', | |
email: '', | |
message: '', | |
} | |
const { state, dispatch } = useHttpReducer() | |
const { theme } = useMediaQueries() | |
return ( | |
<Formik | |
initialValues={initVals} | |
validationSchema={Yup.object().shape({ | |
email: Yup.string() | |
.email('Email not valid') | |
.required('Email is required'), | |
name: Yup.string().required('Required.'), | |
message: Yup.string().required('Required.'), | |
})} | |
onSubmit={async (values, { setSubmitting, resetForm }) => { | |
dispatch({ | |
type: 'SENDING', | |
}) | |
try { | |
await axios.post('/api/contact', values) | |
dispatch({ | |
type: 'SUCCESS', | |
}) | |
setSubmitting(false) | |
resetForm() | |
} catch { | |
dispatch({ | |
type: 'ERROR', | |
}) | |
} | |
}} | |
> | |
<Form | |
style={{ | |
paddingTop: '32px', | |
maxWidth: '800px', | |
width: '100%', | |
textAlign: 'center', | |
}} | |
> | |
<Field name='name'> | |
{({ | |
field, // { name, value, onChange, onBlur } | |
meta, | |
}: FieldProps) => ( | |
<Box pb={2}> | |
<RDTextField | |
required | |
fullWidth | |
disabled={state.loading} | |
id='name' | |
label='Name' | |
name={field.name} | |
value={field.value} | |
onChange={field.onChange} | |
onBlur={field.onBlur} | |
error={meta.touched && Boolean(meta.error)} | |
touched={meta.touched} | |
errorText={meta.touched && meta.error} | |
/> | |
</Box> | |
)} | |
</Field> | |
<Field name='email'> | |
{({ | |
field, // { name, value, onChange, onBlur } | |
meta, | |
}: FieldProps) => ( | |
<Box pb={2}> | |
<RDTextField | |
disabled={state.loading} | |
required | |
fullWidth | |
id='email' | |
label='E-mail Address' | |
name={field.name} | |
value={field.value} | |
onChange={field.onChange} | |
touched={meta.touched} | |
onBlur={field.onBlur} | |
error={meta.touched && Boolean(meta.error)} | |
errorText={meta.touched && meta.error} | |
helperText={`We'll use this email to respond to your message.`} | |
/> | |
</Box> | |
)} | |
</Field> | |
<Field name='message'> | |
{({ | |
field, // { name, value, onChange, onBlur } | |
meta, | |
}: FieldProps) => ( | |
<Box pb={2}> | |
<RDTextField | |
disabled={state.loading} | |
fullWidth | |
multiline | |
rows={5} | |
maxRows={10} | |
id='message' | |
label='Message' | |
name={field.name} | |
value={field.value} | |
onChange={field.onChange} | |
onBlur={field.onBlur} | |
touched={meta.touched} | |
error={meta.touched && Boolean(meta.error)} | |
errorText={meta.touched && meta.error} | |
/> | |
</Box> | |
)} | |
</Field> | |
{state.error && ( | |
<Typography py='16px' variant='subtitle2' color='error'> | |
An error occurred. | |
</Typography> | |
)} | |
{state.success && ( | |
<Typography | |
variant='subtitle2' | |
py='16px' | |
color={theme.palette.success.main} | |
> | |
Success! You will hear from us shortly! | |
</Typography> | |
)} | |
<Button | |
disabled={state.loading} | |
variant='contained' | |
color='primary' | |
sx={{ | |
width: '100%', | |
px: 3, | |
py: 1, | |
}} | |
type='submit' | |
endIcon={<EmailOutlined />} | |
> | |
Send Now | |
</Button> | |
</Form> | |
</Formik> | |
) | |
} | |
export default ContactForm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useReducer } from 'react' | |
type HttpReducerState = { | |
loading: boolean | |
error: boolean | |
success: boolean | |
} | |
/** | |
* Based on code from this article -> https://www.sumologic.com/blog/react-hook-typescript/ | |
*/ | |
type HttpReducerAction = | |
| { type: 'SENDING' } | |
| { type: 'ERROR' } | |
| { type: 'SUCCESS' } | |
const initialState = { | |
loading: false, | |
error: false, | |
success: false, | |
} | |
const reducer = (state: HttpReducerState, action: HttpReducerAction) => { | |
switch (action.type) { | |
case 'SENDING': | |
return { | |
...state, | |
loading: true, | |
success: false, | |
error: false, | |
} | |
case 'ERROR': | |
return { | |
...state, | |
loading: false, | |
success: false, | |
error: true, | |
} | |
case 'SUCCESS': | |
return { | |
...state, | |
loading: false, | |
success: true, | |
error: false, | |
} | |
default: | |
return state | |
} | |
} | |
const useHttpReducer = () => { | |
const [state, dispatch] = useReducer(reducer, initialState) | |
return { | |
state, | |
dispatch, | |
} | |
} | |
export default useHttpReducer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment