Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,41 +132,11 @@ const bazzaFilterColumnConfigs: BazzaFilterColumnConfig[] = [
// Add more configs for other filterable columns as needed
];

// Log all options for each config to help debug undefined labels
bazzaFilterColumnConfigs.forEach((config) => {
// Log the options array for each config, if present
console.log(`Config id: ${config.id}, options:`, config.options);
if (config.options) {
config.options.forEach((opt, idx) => {
// Log if label is missing or undefined
if (!opt || typeof opt.label !== 'string') {
// eslint-disable-next-line no-console
console.warn(`Option label is missing or not a string in config '${config.id}' at index ${idx}:`, opt);
}
});
}
});

function DataTableRouterFormExample() {
const loaderData = useLoaderData<DataResponse>();
const data = loaderData?.data ?? [];
const pageCount = loaderData?.meta.pageCount ?? 0;

// Log options before rendering to catch runtime issues
bazzaFilterColumnConfigs.forEach((config) => {
if (config.options) {
config.options.forEach((opt, idx) => {
if (!opt || typeof opt.label !== 'string') {
// eslint-disable-next-line no-console
console.warn(
`[Render] Option label is missing or not a string in config '${config.id}' at index ${idx}:`,
opt,
);
}
});
}
});

return (
<div className="container mx-auto py-10">
<h1 className="text-2xl font-bold mb-4">Users Table (Bazza UI Filters)</h1>
Expand Down
6 changes: 3 additions & 3 deletions apps/docs/src/remix-hook-form/date-picker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,15 @@ export const Default: Story = {
// Find any available day button in the calendar (look for a button with a number)
// We'll look for any day button that's not disabled
const dayButtons = await dialogCanvas.findAllByRole('button');
const availableDayButton = dayButtons.find(button => {
const availableDayButton = dayButtons.find((button) => {
const text = button.textContent;
return text && DAY_BUTTON_REGEX.test(text.trim()) && !button.hasAttribute('disabled');
});

if (!availableDayButton) {
throw new Error('No available day button found in calendar');
}

await userEvent.click(availableDayButton);
});

Expand Down
36 changes: 18 additions & 18 deletions apps/docs/src/remix-hook-form/form-error-basic.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ const formSchema = z.object({
type FormData = z.infer<typeof formSchema>;

const BasicFormErrorExample = () => {
const fetcher = useFetcher<{
message?: string;
errors?: Record<string, { message: string }>
const fetcher = useFetcher<{
message?: string;
errors?: Record<string, { message: string }>;
}>();

const methods = useRemixForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
Expand All @@ -40,30 +40,30 @@ const BasicFormErrorExample = () => {
<RemixFormProvider {...methods}>
<fetcher.Form onSubmit={methods.handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
<h2 className="text-xl font-semibold text-gray-900">Login Form</h2>

{/* Form-level error display */}
<FormError className="mb-4" />

<TextField
name="email"
type="email"
label="Email Address"
placeholder="Enter your email"
disabled={isSubmitting}
/>

<TextField
name="password"
type="password"
label="Password"
placeholder="Enter your password"
disabled={isSubmitting}
/>

<Button type="submit" disabled={isSubmitting} className="w-full">
{isSubmitting ? 'Signing In...' : 'Sign In'}
</Button>

{fetcher.data?.message && (
<div className="mt-4 p-4 bg-green-50 border border-green-200 rounded-md">
<p className="text-green-700 font-medium">{fetcher.data.message}</p>
Expand All @@ -76,28 +76,28 @@ const BasicFormErrorExample = () => {

const handleFormSubmission = async (request: Request) => {
const { data, errors } = await getValidatedFormData<FormData>(request, zodResolver(formSchema));

if (errors) {
return { errors };
}

// Simulate server-side authentication
if (data.email === 'wrong@email.com' && data.password === 'wrongpass') {
return {
errors: {
_form: { message: 'Invalid email or password. Please try again.' }
}
_form: { message: 'Invalid email or password. Please try again.' },
},
};
}

if (data.email === 'user@example.com' && data.password === 'password123') {
return { message: 'Login successful! Welcome back.' };
}

return {
errors: {
_form: { message: 'Invalid email or password. Please try again.' }
}
_form: { message: 'Invalid email or password. Please try again.' },
},
};
};

Expand Down Expand Up @@ -191,7 +191,7 @@ The FormError component automatically displays when \`errors._form\` exists in t

// Wait for form-level error to appear
await expect(canvas.findByText(/invalid email or password/i)).resolves.toBeInTheDocument();

// Verify field-level errors are cleared
expect(canvas.queryByText(/please enter a valid email address/i)).not.toBeInTheDocument();
});
Expand Down
50 changes: 31 additions & 19 deletions apps/docs/src/remix-hook-form/form-error-custom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@ type FormData = z.infer<typeof formSchema>;
const AlertErrorMessage = (props: React.ComponentProps<typeof FormMessage>) => (
<div className="flex items-center p-4 bg-red-50 border-l-4 border-red-400 rounded-md">
<svg className="h-5 w-5 text-red-400 mr-3" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
<FormMessage className="text-red-800 font-medium" {...props} />
</div>
);

const CustomStyledFormErrorExample = () => {
const fetcher = useFetcher<{
message?: string;
errors?: Record<string, { message: string }>
const fetcher = useFetcher<{
message?: string;
errors?: Record<string, { message: string }>;
}>();

const methods = useRemixForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
Expand All @@ -51,34 +55,34 @@ const CustomStyledFormErrorExample = () => {
<RemixFormProvider {...methods}>
<fetcher.Form onSubmit={methods.handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
<h2 className="text-xl font-semibold text-gray-900">Sign In</h2>

{/* Custom styled form error */}
<FormError
<FormError
components={{
FormMessage: AlertErrorMessage,
}}
/>

<TextField
name="email"
type="email"
label="Email Address"
placeholder="Enter your email"
disabled={isSubmitting}
/>

<TextField
name="password"
type="password"
label="Password"
placeholder="Enter your password"
disabled={isSubmitting}
/>

<Button type="submit" disabled={isSubmitting} className="w-full">
{isSubmitting ? 'Signing In...' : 'Sign In'}
</Button>

{fetcher.data?.message && (
<div className="mt-4 p-4 bg-green-50 border border-green-200 rounded-md">
<p className="text-green-700 font-medium">{fetcher.data.message}</p>
Expand All @@ -91,16 +95,16 @@ const CustomStyledFormErrorExample = () => {

const handleFormSubmission = async (request: Request) => {
const { data, errors } = await getValidatedFormData<FormData>(request, zodResolver(formSchema));

if (errors) {
return { errors };
}

// Always show form error for demo purposes
return {
errors: {
_form: { message: 'Authentication service is temporarily unavailable. Please try again in a few minutes.' }
}
_form: { message: 'Authentication service is temporarily unavailable. Please try again in a few minutes.' },
},
};
};

Expand Down Expand Up @@ -188,21 +192,29 @@ The custom component receives all the same props as the default FormMessage comp
await userEvent.click(submitButton);

// Wait for error to appear
await new Promise(resolve => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500));

// Check for error message
const errorMessage = canvas.queryByText(/authentication service is temporarily unavailable/i);
expect(errorMessage).toBeInTheDocument();
});

await step('Verify custom error styling and structure', async () => {
// Wait for error message to be available
await new Promise(resolve => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500));
const errorMessage = canvas.queryByText(/authentication service is temporarily unavailable/i);
expect(errorMessage).toBeInTheDocument();

const errorContainer = errorMessage?.closest('div');
expect(errorContainer).toHaveClass('flex', 'items-center', 'p-4', 'bg-red-50', 'border-l-4', 'border-red-400', 'rounded-md');
expect(errorContainer).toHaveClass(
'flex',
'items-center',
'p-4',
'bg-red-50',
'border-l-4',
'border-red-400',
'rounded-md',
);

const icon = errorContainer?.querySelector('svg');
expect(icon).toBeInTheDocument();
Expand Down
Loading