Skip to content

Conversation

@EdenWuyt
Copy link
Contributor

@EdenWuyt EdenWuyt commented Dec 5, 2025

Change Summary

Add form component with react-hook-form and yup.

Change Form

Fill this up (NA if not available). If a certain criteria is not met, can you please give a reason.

  • The pull request title has an issue number
  • The change works by "Smoke testing" or quick testing
  • The change has tests
  • The change has documentation

Note: please check out http://localhost:3000/test/form for simple tests. However I do think the test is incomplete for it does not test complex input types and design.

Other Information

  • Please carefully read the documentation inside /components/form.tsx.
  • I think my current implementation is a bit complicated. Since the input field components requires value and onChange props, register does not seem to work (or it works but I jsut dont figure it out).
  • Not that confident about the implementation. Please assign a robot for me first.

Demo:

form_demo.mp4

Related issue

@EdenWuyt EdenWuyt marked this pull request as draft December 5, 2025 07:08
@EdenWuyt EdenWuyt marked this pull request as ready for review December 5, 2025 07:48
@EdenWuyt
Copy link
Contributor Author

EdenWuyt commented Dec 5, 2025

Demo with more test

form_demo_more.mp4

@EdenWuyt EdenWuyt requested a review from ErikaKK December 5, 2025 11:18
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"yup": "^1.7.1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry we'll be using zod for the schema validation instead of yup because it's more suitable for typescript

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a reusable Form component using react-hook-form and zod for validation, addressing issue #53. The implementation provides a declarative API with validation support and flexible error handling options.

  • Adds Form and ControlledField components with zod schema validation
  • Includes comprehensive documentation and three example implementations
  • Adds required dependencies: @hookform/resolvers, react-hook-form, and zod

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 7 comments.

File Description
client/src/components/ui/form.tsx New Form component with react-hook-form integration, ControlledField wrapper, and error handling utilities
client/src/app/test/form/page.tsx Test page demonstrating three error handling approaches with example form schemas and input fields
client/package.json Added dependencies for form validation: @hookform/resolvers, react-hook-form, and zod
client/package-lock.json Lock file updates for new form-related dependencies
Files not reviewed (1)
  • client/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1,150 @@
// Usage of Form:
// import { Form, ControlledField } from "@/components/ui/form"; // Note: both are necessary and not default export!
// <Form schema={zodSchema} // Note: according to the current implimentation, numbers are also string. For an example please refer to /app/test/form/page.tsx
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "implimentation" should be "implementation".

Suggested change
// <Form schema={zodSchema} // Note: according to the current implimentation, numbers are also string. For an example please refer to /app/test/form/page.tsx
// <Form schema={zodSchema} // Note: according to the current implementation, numbers are also string. For an example please refer to /app/test/form/page.tsx

Copilot uses AI. Check for mistakes.
// import { Form, ControlledField } from "@/components/ui/form"; // Note: both are necessary and not default export!
// <Form schema={zodSchema} // Note: according to the current implimentation, numbers are also string. For an example please refer to /app/test/form/page.tsx
// onSubmit={submitHandler} // callback function in the form of (data) => handleSumit(data)
// orError={errorHandler} // optional, allows flexibility of error message display,
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "orError" should be "onError" to match the actual prop name.

Suggested change
// orError={errorHandler} // optional, allows flexibility of error message display,
// onError={errorHandler} // optional, allows flexibility of error message display,

Copilot uses AI. Check for mistakes.
};

// To make input validation effective, an extra wrapper is needed for the input field
type controlledFieldProps<TSchema> = {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type name controlledFieldProps does not follow TypeScript naming conventions. Type names should use PascalCase. Consider renaming to ControlledFieldProps.

Copilot uses AI. Check for mistakes.
Comment on lines 97 to 112
<>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit(onSubmit, (errors) => {
if (onError) onError(errors);
else alert(getErrorMessage(errors));
})}
className={cn(
"flex w-full flex-col space-y-3 rounded-md border border-[hsl(var(--border))] bg-background px-8 py-8",
className,
)}
>
{typeof children === "function" ? children(methods) : children}
</form>
</FormProvider>
</>
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty fragment wrapper (<></>) around FormProvider serves no purpose and adds unnecessary nesting. Consider removing it to simplify the component structure.

Suggested change
<>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit(onSubmit, (errors) => {
if (onError) onError(errors);
else alert(getErrorMessage(errors));
})}
className={cn(
"flex w-full flex-col space-y-3 rounded-md border border-[hsl(var(--border))] bg-background px-8 py-8",
className,
)}
>
{typeof children === "function" ? children(methods) : children}
</form>
</FormProvider>
</>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit(onSubmit, (errors) => {
if (onError) onError(errors);
else alert(getErrorMessage(errors));
})}
className={cn(
"flex w-full flex-col space-y-3 rounded-md border border-[hsl(var(--border))] bg-background px-8 py-8",
className,
)}
>
{typeof children === "function" ? children(methods) : children}
</form>
</FormProvider>

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 63
const schema = z.object({
firstname: z.string().min(1, "Firstname is required"),
lastname: z.string().min(1, "Lastname is required"),
middlename: z.string().optional(),
age: z
.string()
.min(1, "Age is required")
.regex(/^\d+$/, { message: "Age must be a non-negative integer" })
.refine(
(val) => {
const num = Number(val);
return num >= 1 && num <= 120;
},
{
message: "Age must be between 1 and 120",
},
),
});

const extendedSchema = z.object({
firstname: z.string().min(1, "Firstname is required"),
lastname: z.string().min(1, "Lastname is required"),
middlename: z.string().optional(),
age: z
.string()
.min(1, "Age is required")
.regex(/^\d+$/, { message: "Age must be a non-negative integer" })
.refine(
(val) => {
const num = Number(val);
return num >= 1 && num <= 120;
},
{
message: "Age must be between 1 and 120",
},
),
frequency: z.enum(["daily", "weekly", "monthly"], {
error: () => ({ message: "Invalid frequency" }),
}),
amenities: z
.array(
z.enum(
["Audio", "Video", "White Board", "HDMI", "Projector", "Speaker Phone"],
{
error: () => ({ message: "Invalid amenities" }),
},
),
)
.optional(),
recurrenceDate: z.enum(["Monday", "Tuesday", "Wednesday"], {
error: () => ({ message: "Invalid frequency" }),
}),
});
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema and extendedSchema contain duplicated validation logic for firstname, lastname, middlename, and age fields. Consider extracting the common fields into a base schema and extending it using z.object({...}).extend({...}) or composition to reduce code duplication.

Copilot uses AI. Check for mistakes.
)
.optional(),
recurrenceDate: z.enum(["Monday", "Tuesday", "Wednesday"], {
error: () => ({ message: "Invalid frequency" }),
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message for recurrenceDate says "Invalid frequency" which is inconsistent and misleading. It should say "Invalid recurrence date" or similar to match the field name.

Suggested change
error: () => ({ message: "Invalid frequency" }),
error: () => ({ message: "Invalid recurrence date" }),

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,150 @@
// Usage of Form:
// import { Form, ControlledField } from "@/components/ui/form"; // Note: both are necessary and not default export!
// <Form schema={zodSchema} // Note: according to the current implimentation, numbers are also string. For an example please refer to /app/test/form/page.tsx
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment references "zodSchema" but the parameter is named "schema" in the actual component. This inconsistency could confuse developers using this documentation.

Suggested change
// <Form schema={zodSchema} // Note: according to the current implimentation, numbers are also string. For an example please refer to /app/test/form/page.tsx
// <Form schema={schema} // Note: according to the current implimentation, numbers are also string. For an example please refer to /app/test/form/page.tsx

Copilot uses AI. Check for mistakes.
@EdenWuyt
Copy link
Contributor Author

EdenWuyt commented Dec 9, 2025

Copilot suggestion fixed.

@EdenWuyt
Copy link
Contributor Author

No idea why the error comes up
image

@EdenWuyt
Copy link
Contributor Author

The component is rewritten with shadcn style. Please review again. Thanks. Again, no idea why the type check fails. The error message is like below:

Error: src/app/test/form/page.tsx(16,8): error TS1192: Module '"/home/runner/work/bloom-booking-system/bloom-booking-system/client/src/components/ui/input"' has no default export.
Error: Process completed with exit code 2.

@ErikaKK
Copy link
Contributor

ErikaKK commented Jan 6, 2026

No idea why the error comes up image

Please pull the latest main and solve the error, the error is because of input component

…Add detailed documentation about the usage of the components. Frontend styling and test to be done.
…e customization. Update tests and documentation.
…ype of value) => void and (type of event) => void.
…ype of value) => void and (type of event) => void.
…id to handle the issue that errors are empty at the beginning. Components remain intact but update the documentation.
…ema definition and use schema.extend() to reduce redundancy in test file.
@EdenWuyt EdenWuyt force-pushed the issue-53-Add_Form_React_Hook_Form_component branch from eabaa1c to 9ad3479 Compare January 6, 2026 14:53
@EdenWuyt EdenWuyt merged commit d84cd61 into main Jan 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Form (React Hook Form) component

3 participants