nextui
nextui copied to clipboard
[BUG] - react-hook-form
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
Hey @jscode28 could you explain the issue a bit better? I didn't get it
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.
If I replace the NextUI Input Field with a standard HTML input element it works right away.
Did you try using this way @kamzata https://react-hook-form.com/get-started#IntegratingwithUIlibraries ?
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.
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.
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.
For debugging purposes, register from react hook form adds the following props to the input:
{
name: 'title',
onChange: Æ’,
onBlur: Æ’,
ref: Æ’
}
Same issue, defaultValues
does not work. Now need to manually pass defaultValue
.
Same issue,
defaultValues
does not work. Now need to manually passdefaultValue
.
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
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.
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.
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
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.
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
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)?
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}
/>
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?
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 Im willing to take a look, if you can provide a code sandbox example.
@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
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...
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.
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.
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 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
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.
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.
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.
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.