next-netlify-portfolio-starter
next-netlify-portfolio-starter copied to clipboard
Async hook to deal with NextJS and Netlify forms
trafficstars
Hi! Thanks for your blog post, it was very useful
I just wrote a react hook to deal with Netlify forms using NextJS. It solves the problem about requiring a success page, and it works asynchronous. The idea is having a hidden form which its inputs are filled before submission. A target dummy iframe is the target of this form, so redirection is not needed.
import { useReducer, useRef } from 'react'
const FORM_NAME = 'My contact form'
const initialState = {
name : '',
email : '',
msg : ''
}
const reducer = (state, action) => ({
...state,
[ action.name ] : action.value
})
const useNetlifyForm = (fields, formName) => {
const dummyFrame = 'useNetlifyForm_iframe_' + formName
const iframeHTML = `<iframe name="${ dummyFrame }" id="${ dummyFrame }"></iframe>`
const wrapRef = useRef()
const hiddenForm = (
<div ref={ wrapRef } style={{ display : 'none' }} id={ dummyFrame + '_WRAP' }>
<form name={ formName } netlify="true" target={ dummyFrame }>
{ fields.map(field => <input key={ field } name={ field } type="hidden" />) }
<input type="hidden" name="form-name" value={ formName } />
</form>
<div dangerouslySetInnerHTML={{ __html : iframeHTML }} />
</div>
)
const submit = data => new Promise((resolve, reject) => {
const wrap = wrapRef.current
const form = wrap.querySelector('form')
const iframe = wrap.querySelector('iframe')
const iframeLoadHandler = () => {
let { innerHTML } = iframe.contentDocument.body
const success = innerHTML.includes('Your form submission has been received')
iframe.removeEventListener('load', iframeLoadHandler)
innerHTML = ''
success ? resolve(true) : reject(false)
}
fields.forEach(field => {
form.querySelector(`[name=${ field }]`).value = data[ field ]
})
iframe.addEventListener('load', iframeLoadHandler)
form.submit()
})
return { hiddenForm, submit }
}
export default () => {
const { hiddenForm, submit } = useNetlifyForm([ 'name', 'email', 'msg' ], FORM_NAME)
const [ state, dispatch ] = useReducer(reducer, initialState)
const { name, email, msg } = state
const handleChange = event => {
const { name, value } = event.target
dispatch({ name, value })
}
const handleSubmit = async () => {
try{
const success = await submit(state)
success && alert('Sent!')
}
catch{
alert('Failure :(')
}
}
return (
<div>
{ hiddenForm }
<p>
<label>
Your Name: <input type='text' name='name' value={ name } onChange={ handleChange } />
</label>
</p>
<p>
<label>
Your Email: <input type='email' name='email' value={ email } onChange={ handleChange } />
</label>
</p>
<p>
<label>
Your msg: <input type='text' name='msg' value={ msg } onChange={ handleChange } />
</label>
</p>
<p>
<button onClick={ handleSubmit }>Send</button>
</p>
</div>
)
}
Demo: https://vibrant-meninsky-938a30.netlify.app/
Demo: https://vibrant-meninsky-938a30.netlify.app/
Demo is down :(