React Package
useFormField()
Reference for the useFormField() hook
Overview
export function useFormField<
TOutput extends object,
TPath extends FieldPath.Segments,
>(
form: UseForm<TOutput>,
path: TPath,
registerConfig?: {
defaultValue?: Suppliable<FieldPath.Resolve<TOutput, TPath>>;
overrideInitialValue?: boolean;
},
): FormField<TOutput, TPath> | undefined;
- TOutputrepresents the final validated shape of your form data. It defines the full object structure that the form is expected to produce after successful validation and submission.
- TPathrepresents the strongly-typed path (as segment tuples) pointing to a specific leaf inside
TOutput.
useFormField is a React hook that allows you to access and interact with a specific form field within your components.
It will only re-render the component when the specific field's state changes, making it an efficient way to bind to form fields without causing unnecessary re-renders from other parts of the form.
It provides the field if it exists, or
undefined if the field is not found in the form controller.
This is particularly useful for building custom input components that need to bind to form fields without creating a separate field registration.In cases where you want to register a field directly within the hook call, you can provide an optional
registerConfig object that will be forwarded to the form controller's registerField() method for the specified path.
This allows you to set up field registration and configuration in one step when using the hook.Configurations
form!
UseForm<TOutput>
The form controller instance returned by
useForm(). This is required to access the form's internal state and methods for the specified field.path!
FieldPath.Segments
The field path to the specific field within the form's data structure.
registerConfig.defaultValue?:
Suppliable<FieldPath.Resolve<TOutput, TPath>>
The default value to be used for the field if no initial value is set.
registerConfig.overrideInitialValue?:
boolean
Whether to override any initial value that was set on the form controller, if
defaultValue was used to set the field's initial value.Type Reference
Return type of useFormField() may be non-nullable if registerConfig is provided, since the field will be registered if it doesn't already exist. However, it's generally recommended to handle the possibility of undefined in your components to avoid unexpected errors.
Here are the declaration signature overrides, right from the codebase:
packages/react/src/hooks/useFormField.ts
// May return undefined if the field doesn't exist during runtime
export function useFormField<
TOutput extends object,
TPath extends FieldPath.Segments,
>(
form: UseForm<TOutput>,
path: TPath,
): FormField<TOutput, FieldPath.Resolve<TOutput, TPath>> | undefined;
packages/react/src/hooks/useFormField.ts
// Shall not return undefined since the field will be registered if it doesn't already exist
export function useFormField<
TOutput extends object,
TPath extends FieldPath.Segments,
>(
form: UseForm<TOutput>,
path: TPath,
registerConfig: Parameters<typeof form.controller.registerField<TPath>>[1],
): FormField<TOutput, FieldPath.Resolve<TOutput, TPath>>;
Examples
📚 Basic Controlled Input
Even though
useFormField() can be used to hand craft controlled inputs, it's highly recommended to use the FieldRenderer component instead for better ergonomics and less boilerplate in most cases.import { useForm, type UseForm, useFormField } from "@goodie-forms/react";
interface FormData {
user: {
email: string;
};
}
function EmailInput(props: { form: UseForm<FormData> }) {
const fieldPath = props.form.path.of("user.email");
const emailField = useFormField(props.form, fieldPath);
return (
<div>
<input
type="email"
value={emailField?.value ?? ""}
onChange={(e) => emailField?.setValue(e.target.value)}
onFocus={() => emailField?.touch()}
onBlur={() => emailField?.validate()}
/>
{emailField?.issues.map((issue) => (
<p key={issue.id} style={{ color: "red" }}>
{issue.message}
</p>
))}
</div>
);
}
📚 Derived UI (Reactive Display)
function MyForm() {
const form = useForm({
/* ...omitting */
});
const firstnameField = useFormField(form, form.path.of("user.firstname"));
const lastnameField = useFormField(form, form.path.of("user.lastname"));
const passwordField = useFormField(form, form.path.of("user.password"));
const fullName =
`${firstnameField?.value ?? ""} ${lastnameField?.value ?? ""}`.trim();
const passwordStrength =
(passwordField?.value.length ?? 0 > 8) ? "Strong" : "Weak";
return (
<form>
<div className="form-fields">{/* omitting FieldRenderers */}</div>
<div className="status-hud">
<p>Full Name: {fullName}</p>
{passwordField?.value && <p>Password Strength: {passwordStrength}</p>}
</div>
</form>
);
}
📚 Conditional Rendering (Dynamic Forms)
interface FormData {
isCompany: boolean;
company?: {
name: string;
taxId: string;
};
}
function CompanySection(props: { form: UseForm<FormData> }) {
const isCompany = useFormField(props.form, props.form.path.of("isCompany"));
if (!isCompany.value) return null;
return (
<>
<FieldRenderer
form={props.form}
path={props.form.path.of("company.name")}
render={({ fieldProps }) => (
<div>
<label>Company Name</label>
<input {...fieldProps} />
</div>
)}
/>
<FieldRenderer
form={props.form}
path={props.form.path.of("company.taxId")}
render={({ fieldProps }) => (
<div>
<label>Tax ID</label>
<input {...fieldProps} />
</div>
)}
/>
</>
);
}
📚 Array Field (Dynamic List)
interface FormData {
tags: string[];
}
function TagsInput(props: { form: UseForm<FormData> }) {
const tagsField = useFormField(props.form, props.form.path.of("tags"))!;
const addTag = () => {
const newTag = prompt("Enter a new tag");
if (newTag) {
tagsField.modifyValue((tagsValue) => {
tagsValue.push(newTag);
});
}
};
const removeTag = (index: number) => {
tagsfield.modifyValue((tagsValue) => {
tagsValue.splice(index, 1);
});
};
return (
<div>
<label>Tags</label>
<ul>
{tagsField?.value?.map((tag, index) => (
<li key={index}>
{tag} <button onClick={() => removeTag(index)}>Remove</button>
</li>
))}
</ul>
<button type="button" onClick={addTag}>
Add Tag
</button>
</div>
);
}
📚 Listening to Field Changes (Side Effects)
interface FormData {
user: {
email: string;
};
}
function useAutoSave(props: { form: UseForm<FormData> }) {
const emailField = useFormField(props.form, props.form.path.of("user.email"));
React.useEffect(() => {
if (emailField?.value) {
console.log("Email changed:", emailField.value);
beacon.send("form_email_changed", { email: emailField.value });
}
}, [emailField?.value]);
return null;
}