Search code examples
reactjstypescriptreact-hooksreact-context

How can I create a button anywhere on the page that submits a form?


I am trying to create a button that I can put anywhere on a page to submit a specific form. I'm interested in using Hooks and Context / Provider pattern to expose this state anywhere in the app.


Solution

  • The code below creates a context / provider that has access to a useState hook for setting the submit function.

    import React from 'react';
    import {createContext, ReactNode, useContext, useEffect, useRef, useState} from 'react';
    
    export type HiddenButtonSubmitContext = {
      submit: () => void,
      setSubmitHandler: React.Dispatch<React.SetStateAction<() => void>>
    }
    
    export const defaultSetSubmitHandler = () => () => {
      // eslint-disable-next-line no-console
      console.log('submit handler was never set')
    }
    
    export const SubmitContext = createContext<HiddenButtonSubmitContext>({
      submit: () => {
        throw new Error('submit not set, make surea all calls to useHiddenButton are within HiddenButtonProvider');
      },
      setSubmitHandler: defaultSetSubmitHandler()
    })
    
    export const useHiddenButton = () => useContext(SubmitContext)
    
    export const HiddenButtonProvider: React.FC<{ children?: ReactNode | undefined }> = ({ children }) => {
      const [submit, setSubmitHandler] = useState(defaultSetSubmitHandler)
      return (
        <SubmitContext.Provider value={{submit, setSubmitHandler}}>
          {children}
        </SubmitContext.Provider>
      )
    }
    
    export const HiddenButton = () => {
      const hiddenButtonRef = useRef<any>()
      const { setSubmitHandler } = useHiddenButton()
    
      useEffect(() => {
        setSubmitHandler(() => () => {
          hiddenButtonRef.current.click()
        })
      }, [setSubmitHandler])
      
      return (
        <input type="submit" style={{ display: 'none' }} ref={hiddenButtonRef} />
      )
    }
    

    Here's the example usage:

    const Btn = () => {
      const { submit } = useHiddenButton()
      return <button onClick={() => submit()}>hi</button>
    }
    
    export function Example () {
      return (
        <HiddenButtonProvider>
          <Btn />
          <form onSubmit={(e) => {
            e.preventDefault()
            console.log('submitted')
          }}>
            <HiddenButton/>
          </form>
        </HiddenButtonProvider>
      )
    }