Millenial2020
Millenial2020

Reputation: 2913

React Hook Forms How to pass the errors as a props using Typescript

I'm defining a useForm.

const { handleSubmit, control, errors } = useForm<{email: string}>();

Now I'm creating a seperate component that will the input and I'm going to pass the useForm props i created above.

This how that Components look like.

type Props<T> = {
  name: FieldName<T>;
  control: Control<T>;
  errors: FieldErrors<T>;
};

const ControlTextInput = <T extends {}>({
  name,
  control,
  errors,
}: Props<T>) => {
  return (
   
    <Controller
    name={name}
    control={control}
    rules={{
      required:'this is required',
    }}
    render={({ onChange }) => (
        <>
            <TextInput
                onChangeText={(text) => {
                onChange(text);
                }}
            />
            {/* Show my error here */}
            {errors.email && (
                <Text style={{ color: "red" }}>
                    {errors.email?.message}
                </Text>
            )}
      </>
    )}
  />
  );
};

I want to use the component like this.

   <ControlTextInput<AnyObject>
                    name="email"
                    errors={errors}
                    control={control}
                  />

I get this error when i hover over the errors.email enter image description here

Upvotes: 13

Views: 27560

Answers (3)

Majo Mrva
Majo Mrva

Reputation: 299

Let me show you example how to type props for passing errors or register method to child component (ChakraUI based):

interface Props<T extends FieldValues> extends UseControllerProps<T> {
    errors: FieldErrors<IFormData>;
    register: UseFormRegister<IFormData>;
}

export const Form = <T extends FieldValues>({
    register,
    errors,
}: Props<T>) => {

Form content:

<FormControl isInvalid={errors?.name? true : false}>
    <Input
        type="text"
        placeholder="Name"
        {...register('name', {
            required: 'This field is required',
        })}
    />
    {errors?.modelName && (
        <FormErrorMessage>
            {errors?.name?.message}
        </FormErrorMessage>
    )}
</FormControl>

Parent component:

export interface IFormData {
    name: string;
}
const {
    register,
    handleSubmit,
    control,
    formState: { errors, isSubmitting },
} = useForm<IFormData>({
    mode: 'onChange',
});

<form onSubmit={handleUpload}>
            <Form
                register={register}
                errors={errors}
                name={''}
            />
</form>

Upvotes: 0

Matvei Kinner
Matvei Kinner

Reputation: 396

React Hook Form exposes type UseControllerProps which accepts generic type T which infers your input value types or in other words the FieldValues type. Initially you define FieldValues type by passing type about your fields to useForm hook (see MyInputTypes below).

interface MyInputTypes {
  email: string;
  password: string;
}

const { register, handleSubmit, watch, formState: { errors } } = useForm<MyInputTypes>();

This means you can create interface which extends UseControllerProps and has your generic T interface Props<T> extends UseControllerProps<T> {}. Type UseControllerProps already includes type definitions for name and control and therefore you will not need to define them separately in your interface (unless you want to or there is a particular requirement / reason to do so). Regarding errors appropriate solution Imo (as you require only error about single field) would be to pass that particular error directly which has type FieldError | undefined. The result looks like below code.

interface Props<T> extends UseControllerProps<T> {
  error: FieldError | undefined
}

Then you can simply use your ControlTextInput as below.

<ControlTextInput name="email" error={errors.email} />

In the Component (which uses ControlTextInput) your generic T must extend FieldValues as eventually, it is this type which infers types about the fields.

As an example ControlTextInput

import React from 'react';
import { Controller, FieldError, FieldValues, UseControllerProps } from 'react-hook-form';

interface Props<T> extends UseControllerProps<T> {
  error: FieldError | undefined;
}

const ControlTextInput = <T extends FieldValues>({ name, control, error }: Props<T>) => {
  return (
    <Controller
      name={name}
      control={control}
      rules={{
        required: 'This is required',
      }}
      render={({ field: { onChange } }) => (
        <>
          <input
            onChange={(text) => {
              onChange(text);
            }}
          />
          {error && <span style={{ color: 'red' }}>{error?.message}</span>}
        </>
      )}
    />
  );
};

export default ControlTextInput;

As an example Component which uses ControlTextInput

import React, { FunctionComponent } from 'react';
import { useForm } from 'react-hook-form';
import ControlTextInput from './ControlTextInput';

interface InputTypes {
  email: string;
  password: string;
}

const Foo: FunctionComponent = () => {
  const {
    formState: { errors },
  } = useForm<InputTypes>();
  return <ControlTextInput name='email' error={errors.email} />;
};

export default Foo;

Here are screenshots with ready code which mimics more or less your approach and solution (same as code above as new to StackOverflow).

ControlTextInput

Component which uses ControlTextInput

Upvotes: 19

Millenial2020
Millenial2020

Reputation: 2913

The way I found a solution is to use error type any errors?: any;

Use the lodash get function const errName = get(errors, name);

Then i can get the error has follow.

{errName && <Text style={{ color: "red" }}>{errName.message}</Text>}

Upvotes: -2

Related Questions