Home > front end >  React Hook Form with Children in React Native
React Hook Form with Children in React Native

Time:06-28

I have a form with ~15 fields where each section is a unique child component. I want to know how to pass data between the parent form and child components(using control because this is react native)

Right now, I see the proper value for testResult in onSubmit logs but data is undefined for some reason. This means my parent form is somehow not picking up the value in the child.

Parent Form:

const Stepper = () => {
  const form = useForm({ defaultValues: {      
      testResult: "",    
      } 
    });

  const {    control,    handleSubmit,    formState: { errors },  } = form;
  const testResult = useWatch({ control, name: "testResult" });

  const onSubmit = (data) => {
    console.log("watched testResult value: ", testResult);
    console.log("form submission data: ", data);
  };
  
  return (
  <WaterStep form={form} />
  <Button onSubmit={handleSubmit(onSubmit())} />
  )
}

Child component:

const WaterStep = ({ form }) => {
  const { control, formState: { errors }, } = form;

  return (
    <Controller
        name="testResult"
        control={control}
        rules={{
          maxLength: 3,
          required: true,
        }}
        render={({ field: onBlue, onChange, value }) => (
          <TextInput
            keyboardType="number-pad"
            maxLength={3}
            onBlur={onBlur}
            onChangeText={onChange}
            value={value}
          />
        )}
      />
)}

Here I'm trying the first approach this answer suggests, but I've also tried the second with useFormContext() in child https://stackoverflow.com/a/70603480/8561357

Additionally, must we use control in React Native? The examples that use register appear simpler, but the official docs are limited for React Native and only show use of control

Update: From Abe's answer, you can see that I'm getting undefined because I'm calling onSubmit callback in my submit button. I mistakenly did this because I wasn't seeing any data getting logged when passing onSubmit properly like this handleSubmit(onSubmit). I still think my issue is that my child component's data isn't being tracked properly by the form in parent

CodePudding user response:

The problem is most likely in this line:

<Button onSubmit={handleSubmit(onSubmit())} />

Since you're executing the onSubmit callback, you're not allowing react-hook-forms to pass in the data from the form. Try replacing it with the following

<Button onSubmit={handleSubmit(onSubmit)} />

CodePudding user response:

For anyone still looking for guidance on using react-hook-form with child components, here's what I found out to work well:

Parent Component:

const Stepper = (props) => {
  const { ...methods } = useForm({
    defaultValues: {
      testResult: "",    
    },
  });

  const onSubmit = (data) => {
    console.log("form submission data: ", data);
  };
  const one rror = (errors, e) => {
    return console.log("form submission errors: ", errors);
  };
return (
  <FormProvider {...methods}>
      <WaterStep
        name="testResult"
        rules={{
          maxLength: 3,
          required: true,
        }}
      />
  <Button onSubmit={handleSubmit(onSubmit)} />
  )
}

Child:

import { useFormContext, useController } from "react-hook-form";

const WaterStep = (props) => {
  const formContext = useFormContext();
  const { formState } = formContext;

  const { name, label, rules, defaultValue, ...inputProps } = props;

  const { field } = useController({ name, rules, defaultValue });

  if (!formContext || !name) {
    const msg = !formContext
      ? "Test Input must be wrapped by the FormProvider"
      : "Name must be defined";
    console.error(msg);
    return null;
  }
  return (
    <View>
      <Text>
        Test Input
        {formState.errors.testResult && <Text color="#F01313">*</Text>}
      </Text>
      <TextInput
        style={{
          ...(formState.errors.phTestResult && {
            borderColor: "#f009",
          }),
        }}
        placeholder="Test Value"
        keyboardType="number-pad"
        maxLength={3}
        onBlur={field.onBlur}
        onChangeText={field.onChange}
        value={field.value}
      />
    </View>
  );
};

Here's what we're doing:

  1. Define useForm() in parent and de-structure all its methods
  2. Wrap child in <FormProvider> component and pass useForm's methods to this provider
  3. Make sure to define name and rules as props for your child component so it can pass these to useController()
  4. In your child component, define useFormContext() and de-structure your props
  5. Get access to the field methods like onChange, onBlur, value by creating a controller. Pass those de-structured props to useController()

You can go to an arbitrary level of nested child, just wrap parents in a <FormProvider> component and pass formContext as prop.

In Ancestor:

...
const { ...methods } = useForm({
        defaultValues: {
          testResult: "",    
        },
      });
    
const onSubmit = (data) => {
   console.log("form submission data: ", data);
};
...
  <FormProvider {...methods}>
    <ChildOne/>
  </FormProvider>

In Parent:

const ChecklistSection = (props) => {    
  const formContext = useFormContext();
  const { formState } = formContext;

  return (
  <FormProvider {...formContext}>
    <WaterStep
    name="testResult"
    rules={{
      maxLength: 3,
      required: true,
    }}
  />
  </FormProvider>
)}

Thanks to https://echobind.com/post/react-hook-form-for-react-native (one of the only resources I found on using react-hook-form with nested components in react-native)


.... And a further evaluation of my blank submission data problem, if you missed it:

As Abe pointed out, the reason I didn't see data or errors being logged upon form submission was because onSubmit was not being called. This was because my custom submission button, which I didn't include in my original question for simplicity's sake, had a broken callback for a completion gesture. I thought I solved onSubmit not being called by passing it as a call onSubmit(), but I was going down the wrong track.

  • Related