1
0
mirror of synced 2026-02-03 18:01:02 -05:00

Add Formik as form option when creating a new app (#926)

(minor)
This commit is contained in:
Brady Pascoe
2020-08-22 20:29:56 -07:00
committed by GitHub
parent 8b0fa91233
commit bcd9e8dbc9
4 changed files with 154 additions and 1 deletions

View File

@@ -65,6 +65,7 @@ export class New extends Command {
const formChoices: Array<{name: AppGeneratorOptions["form"]; message?: string}> = [
{name: "React Final Form", message: "React Final Form (recommended)"},
{name: "React Hook Form"},
{name: "Formik"},
]
const promptResult: any = await this.enquirer.prompt({

View File

@@ -15,7 +15,7 @@ export interface AppGeneratorOptions extends GeneratorOptions {
version: string
skipInstall: boolean
skipGit: boolean
form: "React Final Form" | "React Hook Form"
form: "React Final Form" | "React Hook Form" | "Formik"
}
export class AppGenerator extends Generator<AppGeneratorOptions> {
@@ -70,6 +70,17 @@ export class AppGenerator extends Generator<AppGeneratorOptions> {
)
pkg.dependencies["react-hook-form"] = "6.x"
break
case "Formik":
this.fs.move(
this.destinationPath("_forms/formik/Form.tsx"),
this.destinationPath("app/components/Form.tsx"),
)
this.fs.move(
this.destinationPath("_forms/formik/LabeledTextField.tsx"),
this.destinationPath("app/components/LabeledTextField.tsx"),
)
pkg.dependencies["formik"] = "2.x"
break
}
this.fs.delete(this.destinationPath("_forms"))

View File

@@ -0,0 +1,84 @@
import React, { useState, ReactNode, PropsWithoutRef } from "react";
import { Formik, FormikProps, FormikErrors } from "formik";
import * as z from "zod";
type FormProps<FormValues> = {
/** All your form fields */
children: ReactNode;
/** Text to display in the submit button */
submitText: string;
onSubmit: (values: FormValues) => Promise<void | OnSubmitResult>;
initialValues?: FormikProps<FormValues>["initialValues"];
schema?: z.ZodType<any, any>;
} & Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit">;
type OnSubmitResult = {
FORM_ERROR?: string
[prop: string]: any
}
export const FORM_ERROR = "FORM_ERROR"
export function Form<FormValues extends Record<string, unknown>>({
children,
submitText,
schema,
initialValues,
onSubmit,
...props
}: FormProps<FormValues>) {
const [formError, setFormError] = useState<string | null>(null);
return (
<Formik<FormValues>
initialValues={initialValues || {} as FormValues}
validate={(values) => {
if (!schema) return;
try {
schema.parse(values);
} catch (error) {
return error.formErrors.fieldErrors;
}
}}
onSubmit={async (values, {setErrors}) => {
const {FORM_ERROR, ...otherErrors} = (await onSubmit(values as FormValues)) || {}
if(FORM_ERROR) {
setFormError(FORM_ERROR);
}
if(Object.keys(otherErrors).length > 0) {
setErrors(otherErrors as FormikErrors<FormValues>)
}
}}
>
{({ handleSubmit, isSubmitting, }) => (
<form
onSubmit={handleSubmit}
className="form"
{...props}
>
{/* Form fields supplied as children are rendered here */}
{children}
{formError && (
<div role="alert" style={{ color: "red" }}>
{formError}
</div>
)}
<button type="submit" disabled={isSubmitting}>
{submitText}
</button>
<style global jsx>{`
.form > * + * {
margin-top: 1rem;
}
`}</style>
</form>
)}
</Formik>
);
}
export default Form;

View File

@@ -0,0 +1,57 @@
import React, { PropsWithoutRef } from "react";
import { useField, useFormikContext, ErrorMessage } from "formik";
export interface LabeledTextFieldProps
extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
/** Field name. */
name: string;
/** Field label. */
label: string;
/** Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number";
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>;
}
export const LabeledTextField = React.forwardRef<
HTMLInputElement,
LabeledTextFieldProps
>(({ name, label, outerProps, ...props }, ref) => {
const [input] = useField(name);
const { isSubmitting } = useFormikContext();
return (
<div {...outerProps}>
<label>
{label}
<input {...input} disabled={isSubmitting} {...props} ref={ref} />
</label>
<ErrorMessage name={name}>
{(msg) => (
<div role="alert" style={{ color: "red" }}>
{msg}
</div>
)}
</ErrorMessage>
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
input {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
border: 1px solid purple;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
);
});
export default LabeledTextField;