cassidoo / next-netlify-portfolio-starter

A personal portfolio project starter using Next.js and Netlify
https://next-portfolio-starter.netlify.app
28 stars 7 forks source link

Async hook to deal with NextJS and Netlify forms #1

Open karlas opened 4 years ago

karlas commented 4 years ago

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>
  )
}
karlas commented 4 years ago

Demo: https://vibrant-meninsky-938a30.netlify.app/

TechStacker commented 4 years ago

Demo: https://vibrant-meninsky-938a30.netlify.app/

Demo is down :(