Last active
January 18, 2023 10:29
-
-
Save LarsHassler/ec1aca6d9edc4287d5a57ae66b2ac208 to your computer and use it in GitHub Desktop.
Example of some Remix-Form Issues I can't solve
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { ActionFunction, LoaderArgs } from "@remix-run/node"; | |
import { useState } from "react"; | |
import { json, redirect } from "@remix-run/node"; | |
import { createForm, performMutation } from "remix-forms"; | |
import { z } from "zod"; | |
import { makeDomainFunction } from "domain-functions"; | |
import { | |
Form as FrameworkForm, | |
useActionData, | |
useSubmit, | |
useTransition as useNavigation, | |
useLoaderData, | |
} from "@remix-run/react"; | |
const Form = createForm({ | |
component: FrameworkForm, | |
useNavigation, | |
useSubmit, | |
useActionData, | |
}); | |
export async function loader({ request, params }: LoaderArgs) { | |
const user = { | |
name: "John Doe", | |
email: "john@example.com", | |
emailVerifiedAt: null, | |
}; | |
return json({ user }); | |
} | |
export default function MinimalFormExample() { | |
const { user } = useLoaderData<typeof loader>(); | |
const actionData = useActionData<typeof action>(); | |
const [ | |
optionalRandomStringFromOutsideOfTheForm, | |
setOptionalRandomStringFromOutsideOfTheForm, | |
] = useState(""); | |
const [indend, setIndend] = useState(""); | |
return ( | |
<> | |
<div style={{ display: "flex", flexDirection: "column", rowGap: "24px" }}> | |
{actionData?.saved && ( | |
<div>Data has been saved, please verify your email</div> | |
)} | |
{/* | |
This button similates some side effects. I need to get the value from input, send it from the server (before submitting the form) | |
and then prefill some following inputs based on a response from the server with the given user input. | |
But I can't get the value to show up. See Problem #1 | |
*/} | |
<button | |
onClick={() => | |
setOptionalRandomStringFromOutsideOfTheForm( | |
`${new Date().getTime()}` | |
) | |
} | |
> | |
Update a hidden input on the form, might not to be clicked by the user | |
</button> | |
<Form | |
schema={formDataSchema} | |
values={{ | |
optionalRandomStringFromOutsideOfTheForm, | |
name: user.name, | |
email: user.email, | |
intend: indend, | |
}} | |
mode="onBlur" | |
style={{ display: "flex", flexDirection: "column", rowGap: "24px" }} | |
> | |
{({ Field, submit, Errors }) => ( | |
<> | |
<Errors /> | |
<Field name="name"></Field> | |
{/***** π€ Problem #1: Values from outside the form */} | |
{/* | |
This field unfortunately is not been updated if the state of the compoent gets updated. | |
This is actually a hidden field, but for this demo I've added it as visible field. | |
The value only changes with the button out side of the form. | |
*/} | |
<Field name="optionalRandomStringFromOutsideOfTheForm"></Field> | |
{/* | |
The following solution is my current way of getting around the issue π with the state variable. | |
I just remove the Field Component and render a native hidden input myself. | |
// π€·ββοΈ Can this be solved differently? | |
*/} | |
<label> | |
The following input is not a Field, but should be the same as | |
the input above. | |
</label> | |
<input | |
type="text" | |
name="randomString" | |
value={optionalRandomStringFromOutsideOfTheForm} | |
/> | |
{/***** π€ Problem #2: Disabled input */} | |
<Field name="email"> | |
{({ Label, SmartInput, Errors }) => ( | |
<> | |
<Label>E-Mail</Label> | |
{/* | |
Depending on the state of the users current data I want to disable the input, | |
so that he/she can't change the email. | |
But I still want to have it required within the schema, as other users need to | |
add their email here, and it should show an error if it's is not a valid email. | |
So far adding a hidden input with the same name as the SmartInput works, it gets | |
passed through to the rendered input. But it's not valid typescript, as disabled | |
is not a valid prop for SmartInput. | |
I think this is similar to: https://github.com/seasonedcc/remix-forms/issues/84 | |
One other way that I thought of would be to only render the SmartInput in the case, | |
that the user can actually change the email, and then render the something else, | |
which just looks like a disabled input, but I would like to have just the one compontent. | |
// π€·ββοΈ How can I do that here in the best way? | |
*/} | |
<SmartInput disabled={!!user.email} /> | |
{!!user.email && ( | |
<input | |
type={"hidden"} | |
name={"email"} | |
value={user.email} | |
/> | |
)} | |
<Errors /> | |
</> | |
)} | |
</Field> | |
<button type="submit">Main CTA</button> | |
{/***** π€ Problem #3: Setting a form value and submitting */} | |
<input type="hidden" name="indend" value={indend} /> | |
<button | |
type="button" | |
onClick={() => { | |
setIndend("profile"); | |
// I've not found a way to set the value for the hidden input and submit the form at the same time | |
// setTimeout works, but I my view this is not the best solution | |
// π€·ββοΈ Can this be solved differently? | |
setTimeout(() => { | |
submit(); | |
}); | |
}} | |
> | |
Changing the indend and submit, therefore the logic in the | |
Action. | |
</button> | |
</> | |
)} | |
</Form> | |
{/***** π€ Problem #4: Getting live data out of the form */} | |
{/* | |
I would like to use the current value out of the form field (name) to show here. | |
π€·ββοΈ Is a useState for the name and onChange fuction to update the state here the best option? | |
*/} | |
<div>{`Hey \${name}, almost there. I would love to call you by the name you have put into the form, but I'm not sure how.`}</div> | |
</div> | |
</> | |
); | |
} | |
/*** Zod Schemas */ | |
export const formDataSchema = z.object({ | |
email: z.string().email(), | |
name: z.string().min(1), | |
optionalRandomStringFromOutsideOfTheForm: z.optional(z.string()), | |
intend: z.optional(z.string()), | |
}); | |
export const domainFunctionEnvironmentSchema = z.object({ | |
userData: z.object({ | |
emailVerifiedAt: z.nullable(z.date()), | |
}), | |
}); | |
/*** Domain Function */ | |
export const saveProfile = makeDomainFunction( | |
formDataSchema, | |
domainFunctionEnvironmentSchema | |
)(async (values, { userData }) => { | |
if (!userData.emailVerifiedAt) { | |
//sendVerfiyEmail | |
} | |
//stor user data into db | |
return { | |
emailVerifiedAt: !!userData.emailVerifiedAt, | |
}; | |
}); | |
/*** remix ActionFunction */ | |
export const action: ActionFunction = async ({ request }) => { | |
const userData = { | |
name: "John Doe", | |
email: "john@example.com", | |
emailVerifiedAt: null, | |
}; | |
const formData = await request.clone().formData(); | |
const indend = formData.get("indend"); | |
const result = await performMutation({ | |
request, | |
schema: formDataSchema, | |
mutation: saveProfile, | |
environment: { userData }, | |
}); | |
/***** π€ Problem #5: Setting a form value and submitting */ | |
/* | |
I would love to combine it, with the performMutation call, but right now the successPath | |
function does not allow to return null or undefined. | |
As of the code https://github.com/seasonedcc/remix-forms/blob/main/packages/remix-forms/src/mutations.ts#L133 | |
it looks like i would work, but again not valid with the typescript definition. | |
Happy to create a PR for that. | |
*/ | |
if (result.success) { | |
if (result.data.emailVerifiedAt) { | |
if (indend === "profile") { | |
return redirect("/success/page/for/secondary/indend"); | |
} | |
return redirect("/success/page/for/main/indend"); | |
} else { | |
return { saved: true }; | |
} | |
} else return json({ errors: result.errors, values: result.values }); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment