nextui icon indicating copy to clipboard operation
nextui copied to clipboard

[BUG] - react-hook-form

Open jscode28 opened this issue 1 year ago • 27 comments

NextUI Version

2.2.9

Describe the bug

`

<Input type="text" placeholder="0" classNames={{ input: "text-right", }} radius="none" onValueChange={(value: string) => inputChange(value, "amount")} size="sm" {...register("amount", { required: true, })} /> `

It uses React-hook-form and next ui input, and is initialized when the value is entered with setValue.

Your Example Website or App

No response

Steps to Reproduce the Bug or Issue

""

Expected behavior

""

Screenshots or Videos

No response

Operating System Version

masOs

Browser

Chrome

jscode28 avatar Nov 14 '23 04:11 jscode28

Hey @jscode28 could you explain the issue a bit better? I didn't get it

jrgarciadev avatar Nov 14 '23 12:11 jrgarciadev

Same here! I'm using NextUI 2.2.9 and RHF 7.48.2 and it's like the NextUI's input field ignores some props like onChange. Furthermore, NextUI's input field doesn't show anymore the default values passed on RHF. It worked before upgrading.

I tested on Chrome and Firefox on MacOS and Linux.

kamzata avatar Nov 14 '23 20:11 kamzata

If I replace the NextUI Input Field with a standard HTML input element it works right away.

kamzata avatar Nov 15 '23 01:11 kamzata

Did you try using this way @kamzata https://react-hook-form.com/get-started#IntegratingwithUIlibraries ?

jrgarciadev avatar Nov 15 '23 12:11 jrgarciadev

Did you try using this way @kamzata https://react-hook-form.com/get-started#IntegratingwithUIlibraries ?

Do you mean just for testing purpose? I have a pretty complex form and I'd like to avoid replacing all the Input Fields with the Controller component.

kamzata avatar Nov 15 '23 13:11 kamzata

Did you try using this way @kamzata https://react-hook-form.com/get-started#IntegratingwithUIlibraries ?

I had the same issue after upgrading to the latest Nextui version. For me works wrapping the Nextui Input inside react-hook-form Controller.

gustavo-gomez avatar Nov 15 '23 14:11 gustavo-gomez

Agreed. Just upgraded from next ui 2.2.1 -> 2.2.9 and now none of the inputs where I was using register() from React Hook Form populate the default values. Contoller inputs still work. Downgrading for the time being and looking into what changed in Next UI's input component.

 <Input
        placeholder='Enter Title...'
        {...register('title', { required: 'Document Needs A Title' })}
        className='border-b font-mono !text-[1.8rem] !font-bold'
        classNames={{
          input: '!text-[1.8rem]  !font-bold leading-loose ',
        }}
        errorMessage={errors.title?.message}
        isInvalid={!!errors.title}
        variant='underlined'
      />

For example. 'title' has a value passed to the react hook form defaults, but the input now shows the placeholder.

christo9090 avatar Nov 17 '23 05:11 christo9090

For debugging purposes, register from react hook form adds the following props to the input:

{ 
   name: 'title', 
   onChange: Æ’, 
   onBlur: Æ’, 
   ref: Æ’
}

christo9090 avatar Nov 17 '23 05:11 christo9090

Same issue, defaultValues does not work. Now need to manually pass defaultValue.

youwillbe avatar Nov 17 '23 09:11 youwillbe

Same issue, defaultValues does not work. Now need to manually pass defaultValue.

Just a guess then. but if the register() function never returned a default value, I am guessing it passed it using the Ref so now somehow NextUI's update is blocking that.

Going through the releases, the only thing that really changed with the input was updating react Aria and the Stately library was updated

christo9090 avatar Nov 17 '23 20:11 christo9090

For me, wrapping the NextUI Input with a Controller fixes the default value problem, however, the errorMessage is not shown! The input is red, the errorMessage is passed, but it is not displayed... I am also using RHF and Input version 2.1.16. Downgrading to Input version 2.1.9 solves the issue for now.

fighter3005 avatar Nov 22 '23 10:11 fighter3005

wrapping the NextUI Input with a Controller fixes the default value problem

Did you try using this way

This does fix it but it sort of defeats the purpose of using react hook form and embracing uncontrolled components.

christo9090 avatar Nov 22 '23 15:11 christo9090

Current issues that I've discovered so far, when trying to pass {...register("field")}:

  • No default filled values
  • Clear button in input doesn't work
  • Placeholder text doesn't change color on error
  • When clicking reset default values appear, but on hover disappear

Reproduction sandbox: https://codesandbox.io/p/devbox/nextui-v2-react-hook-form-forked-rm9dz8

This used to work in @nextui-org/[email protected], as you can see in the original sandbox: https://codesandbox.io/p/devbox/nextui-v2-react-hook-form-xnwxt8 Updating to @nextui-org/[email protected] breaks the integration with react-hook-form.

I couldn't make this work in my local Next.js 14 app router project, even when downgrading to @nextui-org/[email protected]. Reproduction sandbox Next.js 14: https://codesandbox.io/p/devbox/nextjs-nextui-react-hook-form-ck4vdj

Asamsig avatar Jan 02 '24 19:01 Asamsig

when trying to pass {...register("field")}

I have seen all of these same issues. I dug into this a ton before giving up and just wrapping every Input in my application with the Controller component.

What appears to be happening is register("field") returns a ref but the ref is not applied to the input because the input ref is not exposed correctly in the new versions. I tried to manually apply it a bunch of different ways but it seems like the actual input Ref is not within the props of the Component. There are a bunch of div wrappers between the Component and the input itself.

christo9090 avatar Jan 02 '24 19:01 christo9090

Hey @fighter3005 ,

For me, wrapping the NextUI Input with a Controller fixes the default value problem, however, the errorMessage is not shown! The input is red, the errorMessage is passed, but it is not displayed... I am also using RHF and Input version 2.1.16. Downgrading to Input version 2.1.9 solves the issue for now.

Have you found a way to display error messages on <Input /> components?

I'm doing the same as you (passing in error message prop), but I still couldn't display the error messages

The same is happening when I want to show error messages for the <Autocomplete /> component

Mingyang-Li avatar Jan 04 '24 00:01 Mingyang-Li

Hey @fighter3005 ,

For me, wrapping the NextUI Input with a Controller fixes the default value problem, however, the errorMessage is not shown! The input is red, the errorMessage is passed, but it is not displayed... I am also using RHF and Input version 2.1.16. Downgrading to Input version 2.1.9 solves the issue for now.

Have you found a way to display error messages on components?

I'm doing the same as you (passing in error message prop), but I still couldn't display the error messages

The same is happening when I want to show error messages for the <Autocomplete /> component

@Mingyang-Li Are you using the older Versions (for input 2.1.9) and are you using a controller or register? Also, do you set the component as invalid (isInvalid)?

fighter3005 avatar Jan 04 '24 08:01 fighter3005

I also got it working using the Controller component. For anyone else needing an example, with working default values, values, and error messages, here you go:

import { InputProps } from "@nextui-org/input/dist/input";
import { Input } from "@nextui-org/react";
import React from "react";
import { Control, Controller, FieldValue, FieldValues } from "react-hook-form";

export type FormInputProps = {
  name: string;
  control: Control<any, any>;
} & InputProps;

export const FormInput: React.FC<FormInputProps> = ({ name, ...props }) => {
  return (
    <Controller
      name={name}
      control={props.control}
      render={({ field, fieldState, formState }) => {
        return (
          <Input
            {...props}
            isInvalid={!!formState.errors?.[name]?.message}
            errorMessage={formState.errors?.[name]?.message?.toString()}
            value={field.value}
            onChange={field.onChange}
          />
        );
      }}
    ></Controller>
  );
};

Usage example:

<FormInput
  name={"email"}
  label={"Email"}
  variant={"bordered"}
  control={control}
/>

Caperious avatar Jan 05 '24 17:01 Caperious

I also got it working using the Controller component. For anyone else needing an example, with working default values, values, and error messages, here you go:

import { InputProps } from "@nextui-org/input/dist/input";
import { Input } from "@nextui-org/react";
import React from "react";
import { Control, Controller, FieldValue, FieldValues } from "react-hook-form";

export type FormInputProps = {
  name: string;
  control: Control<any, any>;
} & InputProps;

export const FormInput: React.FC<FormInputProps> = ({ name, ...props }) => {
  return (
    <Controller
      name={name}
      control={props.control}
      render={({ field, fieldState, formState }) => {
        return (
          <Input
            {...props}
            isInvalid={!!formState.errors?.[name]?.message}
            errorMessage={formState.errors?.[name]?.message?.toString()}
            value={field.value}
            onChange={field.onChange}
          />
        );
      }}
    ></Controller>
  );
};

Usage example:

<FormInput
  name={"email"}
  label={"Email"}
  variant={"bordered"}
  control={control}
/>

Is the register function required?

lanoow avatar Jan 12 '24 09:01 lanoow

Hey @fighter3005 ,

For me, wrapping the NextUI Input with a Controller fixes the default value problem, however, the errorMessage is not shown! The input is red, the errorMessage is passed, but it is not displayed... I am also using RHF and Input version 2.1.16. Downgrading to Input version 2.1.9 solves the issue for now.

Have you found a way to display error messages on components? I'm doing the same as you (passing in error message prop), but I still couldn't display the error messages The same is happening when I want to show error messages for the <Autocomplete /> component

@Mingyang-Li Are you using the older Versions (for input 2.1.9) and are you using a controller or register? Also, do you set the component as invalid (isInvalid)?

@fighter3005 Yes I have. I even set isInvalid hardcoded to true just to see if the <Autocomplete /> component shows error messages - it doesn't make a difference

Mingyang-Li avatar Jan 16 '24 01:01 Mingyang-Li

@Mingyang-Li Im willing to take a look, if you can provide a code sandbox example.

Caperious avatar Jan 16 '24 08:01 Caperious

@Mingyang-Li Im willing to take a look, if you can provide a code sandbox example.

@Caperious Thanks for the response. You can try out my live app at https://trihard-dev.vercel.app/sessions (to create "Training Sessions" via a form)

It requires authentication, so feel free to make an account

If you're interested in the source code only, here it is: https://github.com/NorthHarbourTriathlonClub/trihard/blob/main/packages/portal/src/features/forms/create-training-session-form.tsx

Mingyang-Li avatar Jan 16 '24 11:01 Mingyang-Li

If you have both nextui validation props (isRequired, minLength, maxLength) and RHF validation they might conflict - possibly there might be some race condition between html, nextui, and RHF validation running.

I had 2 forms that I changed to use <Controller> to get working again, but a few weeks later one of them had stopped working again while the other nearly identical one was still fine. In the one that stopped working the RHF validation doesn't seem to get triggered.

Removing the nextui validation props fixed RHF validation for me. I don't know why I had both set in the first place...

tetslee avatar Jan 24 '24 13:01 tetslee

I have also issues with RHF and NextUI Input component. I am using the latest releases of both packages. In an older project it was working as expected.

kamami avatar Jan 25 '24 11:01 kamami

If you have both nextui validation props (isRequired, minLength, maxLength) and RHF validation they might conflict - possibly there might be some race condition between html, nextui, and RHF validation running.

I had 2 forms that I changed to use to get working again, but a few weeks later one of them had stopped working again while the other nearly identical one was still fine. In the one that stopped working the RHF validation doesn't seem to get triggered.

Removing the nextui validation props fixed RHF validation for me. I don't know why I had both set in the first place...

@tetslee you did get the latest version (2.1.16) of the nextUI input component to work with RHF (show red label & border as well as display the error message)? For me, the error message is not shown regardless of any props I might set or not set for the Input component. The RHF validation is triggered.

fighter3005 avatar Jan 25 '24 13:01 fighter3005

I just pulled my hair out for a couple of hours with this issue too before finding the issue... I won't use the Controller workaround since, as @christo9090 told it before, it defeats RHF purpose... I first tried to downgrade nextui, but it did not work. I ended up rewriting the Input.tsx and Textarea.tsx files into their uncontrolled conterparts.

cvolant avatar Feb 14 '24 01:02 cvolant

@cvolant Can you tell us more about your solution? This one really bugs me too. It seems very easy to get the ref from the register() applied properly.

christo9090 avatar Feb 14 '24 05:02 christo9090

@christo9090

Here is my UncontrolledTextarea.tsx (tweaked from NextUI textarea.tsx):

import { TextareaHTMLAttributes, useCallback, useMemo, useState } from 'react'

import { TextAreaProps, useInput } from '@nextui-org/react'
import { dataAttr } from '@nextui-org/shared-utils'
import { forwardRef } from '@nextui-org/system'
import { mergeProps } from '@react-aria/utils'
import TextareaAutosize from 'react-textarea-autosize'

type NativeTextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>
export type TextareaHeightChangeMeta = {
  rowHeight: number
}
type TextareaAutoSizeStyle = Omit<NonNullable<NativeTextareaProps['style']>, 'maxHeight' | 'minHeight'> & {
  height?: number
}

export const UncontrolledTextarea = forwardRef<
  'textarea',
  Omit<TextAreaProps, 'onChange'> & Pick<NativeTextareaProps, 'onChange'>
>(
  (
    {
      style,
      minRows = 3,
      maxRows = 8,
      cacheMeasurements = false,
      disableAutosize = false,
      onChange,
      onHeightChange,
      value,
      ...otherProps
    },
    ref,
  ) => {
    const {
      Component,
      label,
      description,
      startContent,
      endContent,
      hasHelper,
      shouldLabelBeOutside,
      shouldLabelBeInside,
      errorMessage,
      getBaseProps,
      getLabelProps,
      getInputProps,
      getInnerWrapperProps,
      getInputWrapperProps,
      getHelperWrapperProps,
      getDescriptionProps,
      getErrorMessageProps,
    } = useInput<HTMLTextAreaElement>({ ...otherProps, ref, isMultiline: true })

    const [hasMultipleRows, setHasMultipleRows] = useState(minRows > 1)
    const [isLimitReached, setIsLimitReached] = useState(false)
    const labelContent = label ? <label {...getLabelProps()}>{label}</label> : null

    const inputProps: NativeTextareaProps = useMemo(() => {
      const { onChange: _onChange, value: _value, ...otherInputProps } = getInputProps()
      return { onChange, value, ...otherInputProps }
    }, [getInputProps, onChange, value])

    const handleHeightChange = useCallback(
      (height: number, meta: TextareaHeightChangeMeta) => {
        if (minRows === 1) {
          setHasMultipleRows(height >= meta.rowHeight * 2)
        }
        if (maxRows > minRows) {
          const limitReached = height >= maxRows * meta.rowHeight

          setIsLimitReached(limitReached)
        }

        onHeightChange?.(height, meta)
      },
      [maxRows, minRows, onHeightChange],
    )

    const content = useMemo(
      () =>
        disableAutosize ? (
          <textarea {...inputProps} style={mergeProps(inputProps.style, style ?? {})} />
        ) : (
          <TextareaAutosize
            {...inputProps}
            cacheMeasurements={cacheMeasurements}
            data-hide-scroll={dataAttr(!isLimitReached)}
            maxRows={maxRows}
            minRows={minRows}
            style={mergeProps(inputProps.style as TextareaAutoSizeStyle, style ?? {})}
            onHeightChange={handleHeightChange}
          />
        ),
      [cacheMeasurements, disableAutosize, handleHeightChange, inputProps, isLimitReached, maxRows, minRows, style],
    )

    const innerWrapper = useMemo(() => {
      if (startContent || endContent) {
        return (
          <div {...getInnerWrapperProps()}>
            {startContent}
            {content}
            {endContent}
          </div>
        )
      }

      return <div {...getInnerWrapperProps()}>{content}</div>
    }, [startContent, endContent, getInnerWrapperProps, content])

    return (
      <Component {...getBaseProps()}>
        {shouldLabelBeOutside ? labelContent : null}
        <div {...getInputWrapperProps()} data-has-multiple-rows={dataAttr(hasMultipleRows)}>
          {shouldLabelBeInside ? labelContent : null}
          {innerWrapper}
        </div>
        {hasHelper ? (
          <div {...getHelperWrapperProps()}>
            {errorMessage ? (
              <div {...getErrorMessageProps()}>{errorMessage}</div>
            ) : description ? (
              <div {...getDescriptionProps()}>{description}</div>
            ) : null}
          </div>
        ) : null}
      </Component>
    )
  },
)

UncontrolledTextarea.displayName = 'UncontrolledTextarea'

It does what I need it to do, but I didn't test in in all the edge cases. I have an UncontrolledInput.tsx too if you want.

cvolant avatar Feb 14 '24 13:02 cvolant

I realized that my UncontrolledInput is pointless, because useInput uses useControlledState. It gets the input value, and this value updates getInputProps, so the whole component rerenders and ruins my perfs.

cvolant avatar Mar 02 '24 22:03 cvolant

The ref to the input is not exposed. The forward ref is passed to the top most div element.

Dear maintainers, please either fix the documentation (because it no longer reflects reality): https://nextui.org/docs/components/input#controlled

Note: NextUI Input also supports native events like onChange, useful for form libraries such as Formik and React Hook Form.

or: have it fixed so that ref is passed down again to the input.

AmazingTurtle avatar Mar 21 '24 13:03 AmazingTurtle

The ref to the input is not exposed. The forward ref is passed to the top most div element.

Dear maintainers, please either fix the documentation (because it no longer reflects reality): https://nextui.org/docs/components/input#controlled

Note: NextUI Input also supports native events like onChange, useful for form libraries such as Formik and React Hook Form.

or: have it fixed so that ref is passed down again to the input.

It seems that currently the team is focusing on NextUI Pro. This bug had already been here long time but no one is trying to fix.

djyde avatar Mar 23 '24 06:03 djyde