Home > Software design >  How can I dynamically compose/chain multiple 'handleSubmit' methods in React Hook Form?
How can I dynamically compose/chain multiple 'handleSubmit' methods in React Hook Form?

Time:01-08

I have multiple useForm hooks in my form component. I want to create a method to dynamically handle one submit button for an arbitrary number of hooks. The idea is that the partial forms should be validated successively.

For an isolated scenario, this approach works:

import React from 'react'
import { useForm } from 'react-hook-form'

const MyFormComponent = () => {

   const form1 = useForm();
   const form2 = useForm();

   const onSubmitSuccess = () => {
      //some success logic
   };

   const handleMultiple = form1.handleSubmit(form2.handleSubmit(onSubmitSuccess));

   return <React.Fragment>
      {
         ...form1Fields
      }

      {
         ...form2Fields
      }

      <button onClick={handleMultiple}>submit</button>
   </React.Fragment>
}

Now I want to create a generic 'handleMultiple'. I tried to utilize the redux 'compose' function, but that approach just ignores the validation:

import { compose } from 'redux'

const getHandleMultiple = (submitFunc, ...forms) => {
   const handleMultiple = compose(
      ...forms.map(form => form.handleSubmit),
      submitFunc
   );

   return {
      handleMultiple
   }
}

Any ideas how to archive this? Different approaches are also appreciatied.

EDIT: I tried to wrap the compose-method in a 'useMemo' hook, as @AhmedI.Elsayed suggested, but that didn't solve the problem:

const useComposedFormSubmit = (submitFunc, ...forms) => {
    const handleComposedSubmit = useMemo(() => {
        
        return compose(
            ...forms.map(form => form.handleSubmit), 
            submitFunc
        )

    }, [forms, submitFunc])

    return { 
        handleComposedSubmit
    }
}

EDIT 2: Code sandbox: https://codesandbox.io/s/react-hook-form-compose-handlesubmit-yes5y9

CodePudding user response:

Edit 1: So handleSubmit gets called on your onSubmit and this gets repeated for every handleSubmit we have, this is basically as if we express it mathematically as:

handleSubmit2(handleSubmit1(onSubmit))(x)

but compose?

compose(...forms.map((form) => form.handleSubmit), submitFunc);

This is

handler2(handler1(submitFunc(x)))

and if we composed in opposite order

submitFunc(handler1(handler2(x)))

which is still not what we need. What we need to do is ignore compose because that's not what we need, and implement our own function.

const useComposedFormSubmit = (submitFunc, ...forms) => {
  return useMemo(() => {
    const fns = [...forms.map(f => f.handleSubmit)];
    return fns.reduce((p, c) => {
      return c(p);
    }, submitFunc); 
  }, [submitFunc, forms]); 
};

No reduce version

const useComposedFormSubmit = (submitFunc, ...forms) => {
  return useMemo(() => {
    const fns = [...forms.map(f => f.handleSubmit)];
    let resultFunc = submitFunc;

    for (const fn of fns) {
      resultFunc = fn(resultFunc);
    }

    return resultFunc;
  }, [submitFunc, forms]); 
};

OLD ANSWER, NOT WORKING BUT EXPLAINS HOW TO EXPRESS THE OP POST IN compose

I think the problem is due to forms being undefined at some point, not sure but you can test this one and I'm pretty sure it should work, this is a hook. Hooks should start with use as documented.

const useMultipleSubmitters = (submitFunc, ...forms) => {
   return useMemo(() => {
      return compose(...forms.map(f => form.handleSubmit), submitFunc)
    }, [forms, submitFunc])
}

and adjust as needed. It's the same as yours but recalculates on rerenders.

  • Related