[Bug]: Value of `useControlField` is `undefined` on first render
AlexanderKulia opened this issue · 5 comments
Which packages are impacted?
-
remix-validated-form
-
@remix-validated-form/with-zod
-
@remix-validated-form/with-yup
-
zod-form-data
What version of these packages are you using?
- @remix-validated-form/with-zod": "^2.0.7"
- "remix-validated-form": "^5.1.5"
- "zod": "^3.22.4"
- "zod-form-data": "^2.0.2"
Please provide a link to a minimal reproduction of the issue.
https://github.com/AlexanderKulia/use-control-field-example
Steps to Reproduce the Bug or Issue
- Clone repo
- Run
yarn install
- Run
yarn dev
- Open browser, open console
- Refresh page, field is
undefined
before its values changes to "alex"
Expected behavior
I expected that the field's initial value is set to whatever is passed in defaultValues
similar to useState<string>("alex")
. Am I doing something wrong? Because with this behavior I have to change the type to string | undefined
and handle undefined
which is annoying in complicated dynamic forms
Screenshots or Videos
No response
Platform
- OS: macOS, Linux
- Browser: Chrome
- Version: 121.0.6167.189
Additional context
No response
Have you tried using useEffect
?
export const MyForm = () => {
const [value, setValue] = useControlField(
"myField",
"myForm"
);
useEffect(() => {
// Now we can update the value of the field
// for whatever reason we need.
setValue("Some value");
});
return (
<ValidatedForm validator={myValidator} id="myForm">
<MyInput
name="myField"
// This assumes your input component
// has a `value` and `onChange` prop
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</ValidatedForm>
);
};
Example in here:
https://www.remix-validated-form.io/reference/use-control-field
I'm not quite sure what the example demostrates. I've added useEffect
to my example repo and it does not seem to do anything regardless of how I set it up. In the particular example in the docs useEffect
is executed on every render (deps array is missing) which does not make sense and leads to infinite recursion
P.S. I had a small mistake in my initial example: value
and onChange
were missing. It's corrected now, but it doesn't change the fact that the value is undefined
on first render. Moreover, now I get this
I consider it a bug as well. The workaround is to read the defaultValue
from useField
and set the form element value as value={value ?? defaultValue}
where value
would be read through useControlField
.
This happens only when using useControlField
outside the context of the form itself. This hook has the same limitations mentioned in the "Other Considerations" section on this page. Though admittedly it doesn't look like I documented that anywhere.
I think a potential API improvement we could make here is to allow passing a default value directly into the hook like you mentioned.
But this is more of an intentional (if unfortunate) trade-off of the current API, rather than a bug. If you think about it, in code that looks like this, it's impossible for the useControlField
hook to know anything about the default values of the form.
const { defaultValues } = useLoaderData();
const [value, setValue] = useControlField("myField");
return (
<ValidatedForm
defaultValues={defaultValues}
// ...etc
/>
)
There are 3 main ways to fix the issue your observing.
Manually provide a default
const [firstName = defaultValues.firstName, setFirstName] = useControlField("firstName");
Use setFormDefaults
instead of the defaultValues
prop
export const loader = async () => {
return setFormDefaults(formId, {
firstName: "alex",
lastName: "k",
});
};
Call useControlField
inside the form
const ControlledInput = (props) => {
const [value, setValue] = useControlField(props.name);
return (
<ValidatedInput
value={value}
onChange={e => setFirstName(e.target.value)}
{...props}
/>
);
}