seasonedcc / remix-forms Goto Github PK
View Code? Open in Web Editor NEWThe full-stack form library for Remix and React Router
Home Page: https://remix-forms.seasoned.cc
License: MIT License
The full-stack form library for Remix and React Router
Home Page: https://remix-forms.seasoned.cc
License: MIT License
Have you considered allowing developers to use custom schema validators?
I'd much rather use https://deepkit.io/library/type instead of https://zod.dev
When a date is pushed and read again it shows as one day earlier, because:
when the date string is received in the formAction it is coerced to a Date Object in the local timezone:
https://github.com/SeasonedSoftware/remix-forms/blob/a939fb28bdf31e70af4f7c99883bdc52bc1fa74e/packages/remix-forms/src/coercions.ts#L33
when the Field receives the Date Object, it is converted to string in UTC:
https://github.com/SeasonedSoftware/remix-forms/blob/a939fb28bdf31e70af4f7c99883bdc52bc1fa74e/packages/remix-forms/src/createField.tsx#L69
diff --git a/node_modules/remix-forms/lib/coercions.js b/node_modules/remix-forms/lib/coercions.js
index 0e5b886..5208adb 100644
--- a/node_modules/remix-forms/lib/coercions.js
+++ b/node_modules/remix-forms/lib/coercions.js
@@ -17,10 +17,9 @@ const coerceString = makeCoercion(String, '');
const coerceNumber = makeCoercion(Number, null);
const coerceBoolean = makeCoercion(Boolean, false);
const coerceDate = makeCoercion((value) => {
- if (typeof value !== 'string')
+ if (typeof value !== 'string' || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(value))
return null;
- const [year, month, day] = value.split('-').map(Number);
- return new Date(year, month - 1, day);
+ return new Date(value);
}, null);
function coerceValue(value, shape) {
const { typeName, optional, nullable } = (0, shapeInfo_1.shapeInfo)(shape);
Can someone point me to how can custom styles be applied? I'm using Tailwind.
I'm extending the form as per docs but I can't figure out how to add the className
to the element
multilineComponent={"select"} /* your custom Multiline */
Thank you!
I'm using Mantine and want to supply it's own Input control, rather than the default html input.
So I do something like:
import { Input } from "@mantine/core"
<Form
inputComponent={Input}
schema={schema}
It actually works, But I get a typescript error:
(property) inputComponent?: string | ForwardRefExoticComponent<Pick<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "key" | keyof InputHTMLAttributes<...>> & RefAttributes<...>> | undefined
Type '(<C = "input">(props: PolymorphicComponentProps<C, InputProps>) => ReactElement<any, string | JSXElementConstructor<any>>) & { ...; }' is not assignable to type 'string | ForwardRefExoticComponent<Pick<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "key" | keyof InputHTMLAttributes<...>> & RefAttributes<...>> | undefined'.
Property '$$typeof' is missing in type '(<C = "input">(props: PolymorphicComponentProps<C, InputProps>) => ReactElement<any, string | JSXElementConstructor<any>>) & { ...; }' but required in type 'ForwardRefExoticComponent<Pick<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "key" | keyof InputHTMLAttributes<...>> & RefAttributes<...>>'.ts(2322)
index.d.ts(361, 18): '$$typeof' is declared here.
Form.tsx(75, 3): The expected type comes from property 'inputComponent' which is declared here on type 'IntrinsicAttributes & FormProps<ZodObject<{ email: ZodString; password: ZodString; }, "strip", ZodTypeAny, { email: string; password: string; }, { ...; }>>'
Please can you add an example in the get started section to supply a custom control, rather than breaking out into a 100% custom input.
Thanks!
Really interesting library already!
There does not seem to be support for nested objects and arrays yet, AFAICT.
For example based on a schema like this:
const schema = z.object({
name: z.string(),
contact: z.object({ email: z.string().nonempty().email() }),
addresses: z.array(
z.object({
address: z.string(),
country: z.string()
})
),
});
It seems like it should be pretty easy to do this using register
etc. from react-hook-form
, but it would be nice to have support for that in remix-forms
.
I think we could improve the DX a bit just by exporting the registered input already as a prop.
Currently if we want to make a custom input we need to do this:
<Field name="email">
{({ Label, Errors }) => (
<>
<Label />
<input
type="email"
{...register('email')}
className="border-2 border-dashed rounded-md"
/>
<Errors />
</>
)}
</Field>
Since the Field has the name it would make sense to just do this
<Field name="email">
{({ Label, Errors, props }) => (
<>
<Label />
<input type="email" {...props} className="border-2 border-dashed rounded-md" />
<Errors />
</>
)}
</Field>
While working with Remix forms I'm having trouble understanding which custom component is rendered when.
For example, when does remix-forms
render the fieldErrorsComponent
, when the globalErrorsComponent
and when the errorComponent
.
E.g. is the errorComponent
the default component, but when a fieldErrorsComponent
is specified and there are field specific errors, then that gets rendered instead of the errorComponent
and when there are global errors then the globalErrorsComponent
gets rendered? So when both fieldErrorsComponent
and globalErrorsComponent
are specified, errorsComponent
is superflous?
It would be cool if the docs could answer that question.
We currently require that all fields have labels, once we use labels
prop. We should allow users to add only the labels they want.
Since we are using react-hook-form. It would be nice to have access to stuff like isSubmitting
from the form children without having to pass it down the three.
Right now, the form is controlling the label of the button to indicate the form is submiting but if you are working with UI libs like Chakra, you might want to use somethnig like <Button isLoading />
Hi, I'm just wondering if there's a good way to use formAction
in a resource route.
My use case is that I have a page with multiple different forms. Ideally, each of the forms will be handled by a resource route for reusability. I imagine the behavior would be each resource route will redirect back to the original page with the errors somehow and the form can pick it up and display the error message correctly. I'm not sure if this is technically possible (maybe with a form prefix?).
I'm just wondering if I'm missing something here. Would love to hear your thoughts! Thanks
Only if the user didn't pass them as a prop.
I am trying to make a checkbox required to be true within the zod schema, but the form is still submitting no matter what I have tried so far. There is an example that shows this on the website, but it just sets the default to false. It has a required indicator, but accepts either true or false.
Is there a way to do this currently?
THIS IS MORE A DISCUSSION THAN PROPERLY AN ISSUE.
I'm kinda implementing the formAction
for my remix app and came across with the following situation.
Our form is quite complicated with few dynamic pieces and with some transformations to make the data more appropriate for Prisma and other libraries.
In this particular form, we have a couple of reusable schemas and some really complex ones...
As an example:
export const DaytimeRangeSchema = z.preprocess(
// The field are setting their own index to be able to use the validation
// This can cause a empty array to be set as default value
// So we set empty indexes to undefined to keep their order
arg => Array.apply(0, (arg as []) ?? []),
// Validate the array
TimeRangeSchema.array()
.default([])
// remove undefined values on server
.transform(arg =>
typeof document === 'undefined' ? arg.filter(a => !!a) : arg,
)
// Prisma friendly
.transform(data => data as Prisma.JsonArray),
)
Now, I know this kind of dynamic form is not really "remix way" of doing things but sometimes we'll need to sacrifice the "javascript disabled" in exchange of having a better UX. With that said, coercion should be able to find out what kind of inputs it are dealing with and then coerce it.
I'm not sure exactly how much of these types are really necessary but I kinda have a feeling that some importants are left behind.
Those are the following types I managed to get:
Here is a code example:
const schema = z.object({
_method: z.literal('drop-time'),
index: z.number().int(),
})
<Form
schema={schema}
method="post"
values={{
_method: 'drop-time',
index: 0
}}
/>
remix-form
is only importing concat
and startCase
from lodash
, but depends on the whole package, which is not tree-shakeable. What about switching to lodash-es
or to individual packages (e.g. lodash.concat
)? OR... moving away from lodash?
Can do a PR.
I'm trying to use an <input type="datetime-local" />
(MDN) in my form, and it doesn't seem possible with how Remix Forms is implemented.
Here's my schema:
// Copied from zod docs
// https://github.com/colinhacks/zod#dates
const dateSchema = z.preprocess((arg) => {
if (typeof arg == "string" || arg instanceof Date) return new Date(arg);
}, z.date());
export const schema = z.object({
title: z.string().min(1, { message: "Title is required" }),
details: z.string().min(1, { message: "Details are required" }),
startTime: dateSchema,
endTime: dateSchema,
maxAttendees: z.number().min(2).optional(),
free: z.boolean(),
});
And here's how I'm trying to use these fields in my form:
<Form schema={schema}>
{({ Field, Errors, Button }) => (
{/* Rest of form is excluded */}
<Field name="startTime" label="Starts at" type="datetime-local" />
<Field name="endTime" label="Ends at" type="datetime-local" />
)}
</Form>
When I try to save, I get an error Invalid date
on these inputs. Changing the input type to "date"
fixes the error.
I put a console.log()
inside of z.preprocess()
to see the value of arg
and it prints Invalid Date
in my console instead of the date string. I think what's happening is this function in your code is trying to coerce the string to a date (code assumes there's no timestamp) and it returns "Invalid Date" before it can make it to zod's preprocess function.
I also tried updating my schema to just use z.date()
for startTime
and endTime
but I still get an Invalid date
error.
If there's some way to skip the default coercion behavior, that would probably work. Or maybe it'd better if Remix Forms just natively supported <input type="datetime-local" />
.
Just tried a quick example with formAction mutations but it seems like the day
prop from the schema below is not coercing as expacted.
const TimeSchema = z.string().regex(/\d{4}/)
enum WeekDays {
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6
}
const schema = z.object({
period: z.object({
day: z.nativeEnum(WeekDays),
from: TimeSchema,
until: TimeSchema,
}),
})
The form sends data correctly and inputFromForm
function seems to be loading the data from formData correctly. I think the problem is on getFormValues
that only loop through the first level of keys
How do you do internationalization for the error messages?
Let's say you have a schema
:
const schema = z.object({
name: z
.string()
.min(3, 'A name is required and needs to be at least 3 characters long.'),
});
The leading library for i18n for Remix is currently remix-i18next. It requires the use of a t
function, which is available through the useTranslation
hook.
However, that t
value is in a component. The schema needs to be defined outside of the component, so it can be used in the action
function. How would you solve this?
I have noticed that just setting the value prop e.g. value="myDefaultValue"
yields VERY odd results especially when routing to another route with the same form, sometimes the value is not changed to the new default value.
To clarify, my use case is the following.
After submit I redirect to the /new page which is essentially the form however the <Outlet />
renders it with the default value set as value=""
, in this specific situation the form is not cleared.
The same happens when moving from route to route within the same when it tries to render the form with value="" i.e. trying to clear it.
If you visit the Custom Response example, then click on the Redirect example, and submit the form, you will be redirected to the /success
page
However, if you then click the "Back" button in your browser, you are taken to the Custom Response example (instead of the Redirect example)
As well as the labels, we could capitalize the values if no name
is given ;)
Hi! First, this library looks amazing. I was wondering about 2 things:
tsc output:
node_modules/remix-forms/src/getFormValues.ts:16:56 - error TS2345: Argument of type 'string | string[] | ParsedQs | ParsedQs[] | undefined' is not assignable to parameter of type 'FormDataEntryValue | null'.
Type 'undefined' is not assignable to type 'FormDataEntryValue | null'.
16 values[key as keyof z.infer<Schema>] = coerceValue(value, shape)
So that when we click the label it focuses on the field.
Original request: https://twitter.com/hybrid_alex/status/1497337624629071875
I have a schema with a refinement:
const schema = z.object({
email: z.string().min(1).email(),
password: z.string().min(1),
passwordConfirmation: z.string().min(1)
}).refine((data) => data.password === data.passwordConfirmation, {
message: 'Passwords do not match',
path: ['passwordConfirmation'],
})
I would have expected this to work with remix-forms, ideally with the error either being global or attached to the field belonging to the path. However it is not working. I see a typescript error when passing the schema:
Type 'ZodEffects<ZodObject<{ email: ZodString; password: ZodString; passwordConfirmation: ZodString; }, "strip", ZodTypeAny, { email: string; password: string; passwordConfirmation: string; }, { ...; }>, { ...; }, { ...; }>' is missing the following properties from type 'ZodObject<ZodRawShape, UnknownKeysParam, ZodTypeAny, any, any>': _shape, _unknownKeys, _catchall, _cached, and 16 more.
During runtime, the validation somehow works (if I print during the refine step, I can see that the validation is executed) and the form is submitted. However, validation errors are not shown and I do not get any runtime error / exception (in the browser or on the server side).
Similarly on the schema
- I need to provide the enums/options to the user based on the DB entries:
const schema = z.object({
productCategory: z.enum(["food", "soft-drink", "cosmetics", "other"]), // -- how to get these from the DB? ๐ค
// ...
});
Fingers crossed I haven't missed something super obvious, haha ๐ค๐ผ (guess I did miss the performMutation
example for my Question A ๐
)
Was great meeting y'all in Remix Conf + so far loving the npm/Remix Form ๐ฅณ
How can I define a dynamic successPath
based on some value in the mutation.
Here are the relevant parts:
const mutation = makeDomainFunction(schema)(
async (values) => {
const createdItem = await createItem({
name: "Will it Hurt Me?",
brandName: "Silky Smooth",
})
return createdItem;
}
);
and the action
:
export const action: ActionFunction = async ({ request }) => {
return formAction({
request,
schema,
mutation,
successPath: "/", // ---- this is where I would like to be able to use for example createdItem.slug (which I wouldn't pass from the form, but rather generate on the server)
});
}
Enums are always rendered as <Select />
components.
There should be a way to configure enum
s to be rendered as radio components as well as how the radio group should be rendered, instead of rendering them as select
s.
For example:
import { forwardRef } from 'react';
import type { FormProps } from 'remix-forms';
import { Form as RemixForm } from 'remix-forms';
import type { SomeZodObject } from 'zod';
const Radio = forwardRef<HTMLInputElement, JSX.IntrinsicElements['input']>(
({ type = 'radio', ...props }, reference) => (
<input
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
ref={reference}
type={type}
{...props}
/>
),
);
const Select = forwardRef<HTMLSelectElement, JSX.IntrinsicElements['select']>(
(props, reference) => (
<select
className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
ref={reference}
{...props}
/>
),
);
const Form = <Schema extends SomeZodObject>(props: FormProps<Schema>) => (
<RemixForm<Schema>
className="space-y-8 divide-y divide-gray-200"
radioComponent={Radio}
selectComponent={Select}
renderRadioGroup={({ Radio, Label, options }) => (
<div className="mt-4 space-y-4">
{options.map(({ name, value }) => (
<div className="flex items-center">
<Radio value={value} />
<Label>{name}</Label>
</div>
)}
</div>
)}
{...props}
/>
);
Note: We have to flush out the details here. In a real form with radio inputs, you usually have labels and the inputs linked using id
s and htmlFor
tags.
And then you can use it like this:
import Form from '~/where-your-form-lives';
import { z } from 'zod';
const schema = z.object({
country: z.enum(['United States', 'Canada', 'Mexico']),
pushNotifications: z.enum([
'Everything',
'Same as email',
'No push notifications',
]),
});
export default MyPage() {
return (
<Form radio=[['pushNotifications']] schema={schema}>
{({ Field, Button }) => (
<>
<Field name="country" />
<Field name="pushNotifications" />
<Button />
</>
)
</Form>
)
}
You can hack your input fields today by using register
and rendering your own Radio
s and Label
s - like this:
<fieldset className="mt-6">
<legend className="contents text-base font-medium text-gray-900">
Push Notifications
</legend>
<p className="text-sm text-gray-500">
These are delivered via SMS to your mobile phone.
</p>
<Field
className="mt-4 space-y-4"
name="pushNotifications"
>
{({ Errors }) => (
<>
<div className="flex items-center">
<Radio
{...register('pushNotifications')}
checked
id="push-everything"
value="Everything"
/>
<Label
className="ml-3 block text-sm font-medium text-gray-700"
htmlFor="push-everything"
>
Everything
</Label>
</div>
<div className="flex items-center">
<Radio
{...register('pushNotifications')}
id="push-email"
value="Same as email"
/>
<Label
className="ml-3 block text-sm font-medium text-gray-700"
htmlFor="push-email"
>
Same as email
</Label>
</div>
<div className="flex items-center">
<Radio
{...register('pushNotifications')}
id="push-nothing"
value="No push notifications"
/>
<Label
className="ml-3 block text-sm font-medium text-gray-700"
htmlFor="push-nothing"
>
No push notifications
</Label>
</div>
<Errors />
</>
)}
</Field>
</fieldset>
However, that is very tedious and takes a lot of code.
It would be even better if we could use react-hook-form resolvers for that.
Hello Guys,
So, I'm trying to create a form with a collection of items. I encountered two problems:
The component Errors
on Field children
does not return an error from my collection items for my item name
.
I only can get error messages for this field when I use formState.errors
.
When I submit the valid form the FormData
send the data
in this shape: { name: '', 'items.0.name': '' }
.
Debugging, I discovered that the parser of form date used on remix-domains
inside remix-forms
use the lib qs
to parse as query-string
that lib accepts only brackets notation.
I changed the notation to brackets, I can reach the mutation with correct json structure as { name: '33', items: [ { name: '3' } ] }
and typescript complains about the name of the field.
Argument of type '"items[0][name]"' is not assignable to parameter of type '"name" | "items" | "organization_id" | `items.${number}` | `items.${number}.name`'.ts(2345)
Component
import type { ActionFunction } from "@remix-run/node";
import { formAction } from "remix-forms";
import Input from "~/components/app/Input";
import Form from "~/components/Form";
import { createNewPlan } from "~/domain/plans/createPlan";
import { PlanSchema } from "~/domain/plans/schema";
import { requireOrganizationId, requireUserId } from "~/session.server";
export const action: ActionFunction = async ({ request }) => {
const { organizationId } = await requireOrganizationId(request);
// when uses dot notation print { name: '', 'items.0.name': '' }
console.log(Object.fromEntries(await request.clone().formData()));
return formAction({
request,
schema: PlanSchema,
mutation: createNewPlan,
successPath: "plans",
environment: { organization_id: organizationId },
});
};
export default function NewPlanPage() {
return (
<div className="rounded-xl p-6">
<Form
schema={PlanSchema}
values={{
items: [],
}}
>
{({ Field, Button, Errors, register, formState }) => (
console.log(formState.errors),
(
<>
<Field name="name" />
<Field name="items">
{({ Label, Errors, errors }) => (
console.log(errors, `errors`),
(
<>
<Label />
<Input type="text" {...register(`items.0.name`)} />
<Errors />
<p>{formState.errors.items?.[0]?.name?.message}</p>
</>
)
)}
</Field>
<Errors />
<Button />
</>
)
)}
</Form>
</div>
);
}
Schema:
import z from "zod";
import type { Merge } from "type-fest";
import type { Base } from "~/types/common/base";
export const PlanSchema = z.object({
name: z.string().min(1, `name required`),
items: z.array(
z.object({
name: z.string().min(1, "required name in items"),
})
),
organization_id: z.string().optional(),
});
export type Plan = Merge<PlanForm, Base>;
export type PlanForm = z.infer<typeof PlanSchema>;
Mutation:
export const createNewPlan = makeDomainFunction(
PlanSchema,
EnvironmentSchema
)(
async (values, env) => console.log(values)
);
Remix throws an error Error: cannot clone body after it is used
if I try to access the formData before calling the formAction method
const formData = await request.formData()
Here is a code to reproduce it:
import { ActionFunction } from "@remix-run/server-runtime"
import { InputError, makeDomainFunction } from "remix-domains"
import { Form, formAction } from "remix-forms"
import { z } from "zod"
const schema = z.object({
email: z.string().nonempty().email(),
})
const takenEmails = ["[email protected]", "[email protected]"]
const mutation = makeDomainFunction(schema)(async (values) => {
if (takenEmails.includes(values.email)) {
throw new InputError("Email already taken", "email")
}
return values
})
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData()
return formAction({ request, schema, mutation })
}
export default () => <Form schema={schema} />
this is regarding: https://remix-forms.seasoned.cc/examples/actions/field-error
If I enter [email protected] and submit the form it returns with Email already taken. => Ok
If I change the email to foo the error changes to Invalid email without requiring a submit => not sure if this is a bug or a feature. I think it should only re-validate if I use mode="onChange"
If I now change the email to a valid one like [email protected] the old/cached error appears: Email already taken => Not Ok. I think this is confusing, as it might lead to the assumption that [email protected] is taken too.
My proposal would be to allow resetting errors
For whenever we have values that do not come from the user's input but we need to pass them to the action
Today we're hard-coded to onChange
mode. The user should be able to choose other modes and the UI should adapt to them.
Curiously transforms are not working on server site. I can see on the request payload that transforms are working fine on client side but when it gets to the serve they are ignored. I don't see why we want to have transforms on the client though. If I just transform a string to number on the client side, it should throw an error on the server since the validation would need a string instead of a number.
I am using Remix Forms with Mantine components. I have to use the Mantine rich text editor and that cannot be server rendered. So in order to render it in client, I have a wrapper around it so it only renders when it is in the browser.
Since it is the wrapper I will be using, ...register(descriptio)
does not bind it properly to the control. Although rich text editor is not a simple HTML component, I would want to know how to integrate validations with those type of elements.
I know I am missing something obvious, any help is appreciated. Thanks
richtexteditor.tsx
import type { RichTextEditorProps } from "@mantine/rte";
export function RichText(props: RichTextEditorProps) {
if (typeof window !== "undefined") {
// eslint-disable-next-line import/extensions, global-require
const { RichTextEditor } = require("@mantine/rte");
return (
<RichTextEditor
controls={[
["bold", "italic", "underline", "link"],
["unorderedList", "h1", "h2", "h3"],
["sup", "sub"],
["alignLeft", "alignCenter", "alignRight"],
]}
{...props}
/>
);
}
// Render anything as fallback on server, e.g. loader or html content without editor
return null;
}
usage in page.tsx inside remix form
<Field name="description" className="mt-5 mb-1">
{({ Errors }) => (
<>
<RichText
onChange={(event: any) => {}}
value={description}
placeholder=""
className="mt-1 mb-1"
classNames={{
root: "min-h-[200px] border-gray-300",
}}
/>
<Errors className="mt-1 text-xs font-semibold text-red-500" />
</>
)}
</Field>
Right now, there is no way to give a Field
or a SmartInput
an autoComplete
prop, e.g. autoComplete="username"
.
Let's say you have a custom Input
configured and a Zod schema
like this:
import { forwardRef } from 'react';
import type { FormProps } from 'remix-forms';
import { Form as RemixForm } from 'remix-forms';
import type { SomeZodObject } from 'zod';
import { z } from 'zod';
const Input = forwardRef<HTMLInputElement, JSX.IntrinsicElements['input']>(
({ type = 'text', ...props }, reference) => (
<input
className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
ref={reference}
type={type}
{...props}
/>
),
);
const Form = <Schema extends SomeZodObject>(props: FormProps<Schema>) => (
<RemixForm<Schema>
className="space-y-8 divide-y divide-gray-200"
inputComponent={Input}
{...props}
/>
);
const schema = z.object({
firstName: z.string().min(1),
email: z.string().min(1).email(),
})
And you want to have an input with an autoComplete
property, you have to render it like this:
<Field className="sm:col-span-3" name="firstName">
{({ Label, Errors }) => (
<>
<Label>First name</Label>
<Input
{...register('firstName')}
autoComplete="given-name"
/>
<Errors />
</>
)}
</Field>
<Field className="sm:col-span-3" name="emailAddress">
{({ Label, Errors }) => (
<>
<Label>Email address</Label>
<Input
{...register('emailAddress')}
autoComplete="email"
/>
<Errors />
</>
)}
</Field>
This renders using SmartInput
as well as configuring inputComponent
on the Form
useless in these cases.
Ideally, we could give the Field
or the SmartInput
the autoComplete
property, so we could render:
<Field className="sm:col-span-3" name="firstName">
{({ Label, SmartInput, Errors }) => (
<>
<Label>First name</Label>
<SmartInput autoComplete="given-name" />
<Errors />
</>
)}
</Field>
<Field autoComplete="email" className="sm:col-span-3" name="emailAddress" />
(Note that in this case, we could also render firstName
straight up as a Field
, but I wanted to show the "Custom Field, standard components" version, too.)
We also have to consider this for the Form
's renderField
prop, e.g.:
renderField={({ Field, autoComplete, ...props }) => {
const { name } = props;
return (
<Field key={name as string} {...props}>
{({ Label, SmartInput, Errors }) => (
<>
<Label />
<div className="mt-1">
<SmartInput autoComplete={autoComplete} />
</div>
<Errors />
</>
)}
</Field>
);
}}
Alternatively, maybe there is a way to infer the autoComplete
prop from the schema
for certain all the keys that are possible autoComplete
values.
Hey ๐๐ผ
In my mutation, I'm calling a distant API where validation (not covered by zod) takes place.
In case of problems, the API responds with a 422 error and a JSON describing all problems like this:
{
"username": "This username have been taken",
"email": "Your email provider is banished from this service"
}
I've seen that I can use throw new InputError('Error message', 'field')
for a single field, but is there a way to throw for multiple fields simultaneously?
Thanks !
It would be nice if the result was passed in to this callback along with the request
https://github.com/SeasonedSoftware/remix-forms/blob/896ab6c5077918d19646f3f3c7017ac8f4c2b014/src/formAction.server.ts#L99
This would allow for something along the following lines:
export const action: ActionFunction = async ({ request }) =>
formAction({
request,
schema,
mutation,
beforeSuccess: async ({ request, data }) => {
const session = await getSession(request.headers.get("Cookie"));
session.set("userId", data.userId);
return redirect("/", {
headers: { "Set-Cookie": await commitSession(session) },
});
},
});
Would this be any better than just using performMutation directly? Seems like it might be a tiny bit nicer since by using beforeSuccess, the failure path doesn't need to be re-implemented.
export const action: ActionFunction = async ({ request }) => {
const result = await performMutation({ request, schema, mutation });
if (!result.success) {
return json(result, 400);
}
const session = await getSession(request.headers.get("Cookie"));
session.set("userId", result.data.userId);
return redirect("/", {
headers: { "Set-Cookie": await commitSession(session) },
});
};
Edit: I think I do prefer to use performMutation directly for this case. I guess I am wondering what the ideal use-case for beforeSuccess
would be but I'll go ahead and close this issue for now.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.