Home > Software design >  Submit and handle two forms with one handleSubmit in react-hook-form
Submit and handle two forms with one handleSubmit in react-hook-form

Time:09-07

In my react app, I have two address forms on one page that have two save address functions which save the address in the database. There's also one submit button that submits both two fields and navigates to the next page (The plus button in the circle adds the address to the saved addresses list):

enter image description here

What I want to do is to validate the form fields and I'm already doing that by calling two instances of the useForm() hook:

// useForm hook
  const {
    control: senderControl,
    handleSubmit: handleSenderSubmit,
    setValue: senderSetValue,
  } = useForm({
    defaultValues: {
      senderName: '',
      senderLastName: '',
      senderPostalCode: '',
      senderPhone: '',
      senderAddress: '',
    },
  });

  // useForm hook
  const {
    control: receiverControl,
    handleSubmit: handleReceiverSubmit,
    setValue: receiverSetValue,
  } = useForm({
    defaultValues: {
      receiverName: '',
      receiverLastName: '',
      receiverPhone: '',
      receiverPostalCode: '',
      receiverAddress: '',
    },
  });

I've then added the handleSubmit method of these two hooks to the onClick (onPress in RN) of the plus button for each field respectively.

This does indeed validate the forms individually but the problem arises when I'm trying to submit the whole page with the SUBMIT button.

I still want to be able to validate both of these two address fields when pressing the general SUBMIT button but I have no idea how I can validate these two instances with one handleSubmit and get the return data of both fields' values.

EDIT (CustomInput.js):

const CustomInput = ({
  control,
  name,
  rules = {},
  placeholder,
  secureTextEntry,
  keyboardType,
  maxLength,
  textContentType,
  customStyle,
}) => (
  <Controller
    control={control}
    name={name}
    rules={rules}
    render={({field: {onChange, onBlur, value}, fieldState: {error}}) => (
      <View style={customStyle || styles.container}>
        <TextInput
          value={value}
          onBlur={onBlur}
          onChangeText={onChange}
          placeholder={placeholder}
          keyboardType={keyboardType}
          maxLength={maxLength}
          textContentType={textContentType}
          secureTextEntry={secureTextEntry}
          style={[
            styles.textInput,
            {
              borderColor: !error
                ? GENERAL_COLORS.inputBorder
                : GENERAL_COLORS.error,
            },
          ]}
        />
        {error && <Text style={styles.errMsg}>{error.message || 'error'}</Text>}
      </View>
    )}
  />
);

Usage:

<CustomInput
    control={control}
    name="senderPostalCode"
    rules={{
      required: 'Postal Code is Required',
    }}
    placeholder="Postal Code"
    keyboardType="number-pad"
    textContentType="postalCode"
    customStyle={{
      width: '49%',
      marginBottom: hp(1),
    }}
/>

Is there even any way this can be possible at all?

CodePudding user response:

Thanks to @TalgatSaribayev 's comment for leading me to this solution.

I didn't need to set any specific validation rules for the address field and in the end I separated the sender and receiver forms into two different pages.

First, I've got to point out that instead of getting the input values of postalCode and address fields with the getValues API, I used the useWatch hook to get the most updated values.

// Watch inputs
const watchedInputs = useWatch({
  control,
  name: ['senderPostalCode', 'senderAddress'],
});

When I saved the input values with getValues in a variable, I got the previous state instead of the most recent one and the only way to solve that was calling getValues('INPUT_NAME') whenever I wanted to get the most recent one. (In the end I resolved to use useWatch).

///////////////////////////////////////////////////////////////////////////////////////////////////

As @TalgatSaribayev pointed out, creating just one useForm instance was sufficient enough. All I had to do was to create a function which would set the errors manually and check their validation upon pressing the save address button.

// Check if sender postal code input has error
const senderHasError = () => {
  if (!/^\d $/.test(watchedInputs[0])) {
    setError('senderPostalCode', {
      type: 'pattern',
      message: 'Postal Code must be a number',
    });
    return true;
  }
    
  // Any other rules
  if (YOUR_OWN_RULE) {
    setError('senderPostalCode', {
      type: 'custom',
      message: 'CUSTOM_MESSAGE',
    });
    return true;
  }
    
  // Clear error and return false
  clearErrors('senderPostalCode');
  return false;
};

The problem was that the errors wouldn't get updated (cleared) even when they had passed the validation. As if the invalid input wouldn't attach onChange event listeners to re-validate it. Something that happens as default when you submit the form with the onSubmit mode of useForm. (https://react-hook-form.com/api/useform)

So I resolved to use the trigger API of useForm to manually trigger form validation and listen for the changes on the postalCode field when the save address button is pressed.

First I created a toggle state which changes the trigger state:

// Postal code onSubmit event state
const [triggered, setTriggered] = useState(false);

Then used the trigger API of useForm in a useMemo to trigger the input validation only if the triggered state is set to true and the input field's value has changed:

// Manually trigger input validation if postal code's onSubmit event is true
useMemo(() => {
  if (triggered) trigger('senderPostalCode');
}, [watchedInputs[0]]);

I assume the way I triggered the input field with the trigger API works the same way as useForm's mode of onSubmit does it under the hood: Starting the trigger when the user presses the save address button by changing the trigger state with setTrigger.

// Add address to sender favorites
const handleAddSenderFavAddress = () => {
  // Trigger on the press event
  setTriggered(true);

  // Return if sender postal code input has error
  if (senderHasError()) return;

  // SAVE ADDRESS LOGIC
  ///////////////////////////////
};

This was the only way I managed to validate separate input fields apart from the general validation that occurs with useForm's handleSubmit function.

I welcome any more answers that might lead to a better solution.

CodePudding user response:

You can use a single instance of useForm hook and register all your fields in both the forms using the same register method.

This way whenever you click on submit, it will fetch the values from all the fields registered in different forms using the same register method.

Have attached a code sandbox link for your reference

  • Related