7

Im trying to split the code in this example: https://codesandbox.io/s/usecontroller-0o8px

but Im getting a type error when Im passing control={control} to input component.

(JSX attribute) control?: Control<FieldValues, object> | undefined
Type 'Control<formValues, object>' is not assignable to type 'Control<FieldValues, object>'.
  The types of '_options.resolver' are incompatible between these types.
    Type 'Resolver<formValues, object> | undefined' is not assignable to type 'Resolver<FieldValues, object> | undefined'.
      Type 'Resolver<formValues, object>' is not assignable to type 'Resolver<FieldValues, object>'.ts(2322)
controller.d.ts(22, 5): The expected type comes from property 'control' which is declared here on type 'IntrinsicAttributes & { label?: string | undefined; supportive?: string | undefined; } & UseControllerProps<FieldValues, string> & InputHTMLAttributes<...> & { ...; }'

Here is my input component

type Props = {
  label?: string
  supportive?: string
} & UseControllerProps &
  InputHTMLAttributes<HTMLInputElement>

const InputComp: FC<Props> = ({ label, supportive, ...props }) => {

  ...

  const {
    field,
    fieldState: { invalid, isTouched, isDirty },
    formState: { touchedFields, dirtyFields },
  } = useController(props)

  return <input {...field} />
}

when I pass formValues type to UseControllerProps<formValues> the error is gone.

I want to use the input component in different forms with different formValues How can I achieve that?

3
  • 1
    Have you found the solution? Commented Jan 6, 2022 at 10:10
  • 3
    Yes, I used generic types. My component look like this now: type Props<T> = {..} & UseControllerProps<T> function InputComp<T>(props: Props<T>) {} And then in the form, I pass the <FormType> type, like below: <Input<FormType> name='title' type='text' control={control} /> Commented Jan 15, 2022 at 7:12
  • @Yousefjalali are you able to add your solution as the answer? I'm curious to see your solution Commented May 26, 2022 at 16:17

3 Answers 3

4

I had the same issue but I found this article and the guy solved this passing a T props that extends in a component

https://dev.to/texmeijin/component-design-idea-using-react-hook-form-v7-ie0

Sign up to request clarification or add additional context in comments.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
3

Here's a solution that worked for me. I used this & the link to the original code snippet for help.

I have a reusable Headless UI ListBox component that I wanted to pass dropdown values into along with my original form interface.

Reference to React Hook Form origination:

enum State {
  AL = "AL",
  ...
}

export interface UserRegistration {
  firstName: string
  lastName: string
  streetAddress: string
  streetAddress2: string
  city: string
  state: State
  zipCode: string
}

const registration = () => {

  const {
    control
  } = useForm<UserRegistration>()

  return (
    ...
    
    <Dropdown
      control={control}
      name="state"
      dropdownOptions={State}
    />
  )
}

Reusable Component:

import { Fragment } from 'react'
import { Listbox, Transition } from '@headlessui/react'
import { useController, UseControllerProps } from 'react-hook-form'    

interface DropdownProps {
  dropdownOptions: any
}

const Dropdown = (props: DropdownProps & UseControllerProps<UserRegistration>) => {
  const {
    field: { value, onChange },
  } = useController(props)

  return (
    <div>
      <Listbox value={value} onChange={onChange}>
        <div className="relative">
          <Listbox.Button>
            <span>{value}</span>
            <span>
              <SelectorIcon aria-hidden="true" />
            </span>
          </Listbox.Button>
          <Transition
            as={Fragment}
            leave="transition ease-in duration-100"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <Listbox.Options>
              {Object.keys(props.dropdownOptions).map((value, index) => (
                <Listbox.Option
                  key={index}
                  value={value}
                >
                  {({ selected }) => (
                    <>
                      <span>
                        {value}
                      </span>
                      {selected ? (
                        <span>
                          <CheckIcon aria-hidden="true" />
                        </span>
                      ) : null}
                    </>
                  )}
                </Listbox.Option>
              ))}
            </Listbox.Options>
          </Transition>
        </div>
      </Listbox>
    </div>
  )
}

2 Comments

But won't this Select then be coupled to UserRegistration form only?
Yes, re coupling, but I was able to make a minimal 'controlled wrapper' around the core Listbox component, so the impact is minor. You just really need the useController line and then pass value and onChange down to your reusable component.
1

I have a less elegant solution(Just wrap your custom components with a hook), it works:

custom component:

import { FC } from "react"
import { TextField } from "@mui/material"
import { TextFieldProps } from "@mui/material"
import { Controller, FieldValues, UseControllerProps } from 'react-hook-form'

export const useRHFTextField = <T extends FieldValues>() => {

  const RHFTextField: FC<TextFieldProps & UseControllerProps<T>> = props => {
    const {
      control,
      name,
      defaultValue,
      ...other
    } = props

    return (
      <Controller
        control={control}
        name={name}
        defaultValue={defaultValue}
        render={({ field }) => (
          <TextField
            {...field}
            {...other}
            variant="standard"
          />
        )}
      />
    )
  }

  return RHFTextField
}

useage:

import { useRHFTextField } from "../components/RHFTextField"
...

export const BaseInfo = () => {
  
  const RHFTextField = useRHFTextField<IBaseInfo>()

  const { control,... } = useForm<IBaseInfo>({
    ...
    defaultValues,
  })

  ...

  return (
    <Box component="form" ...>
      <RHFTextField control={control} label="username" name="username" defaultValue="" />
      <RHFTextField control={control} label="password" name="password" defaultValue="" />
    </Box>
  )
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.