Skip to content

Instantly share code, notes, and snippets.

@LarsHassler
Last active January 18, 2023 10:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LarsHassler/ec1aca6d9edc4287d5a57ae66b2ac208 to your computer and use it in GitHub Desktop.
Save LarsHassler/ec1aca6d9edc4287d5a57ae66b2ac208 to your computer and use it in GitHub Desktop.
Example of some Remix-Form Issues I can't solve
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