Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Steven Than committed Nov 17, 2022
1 parent 1ec016f commit ce95b0c
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 81 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"extends": ["@appfolio/eslint-config-appfolio-react", "prettier"],
"plugins": ["no-only-tests", "react-hooks", "@typescript-eslint"],
"rules": {
"camelcase": ["error", { "allow": "^experimental_" }],
"curly": ["error", "all"],
"react/jsx-props-no-spreading": "off",
"react/static-property-placement": "off",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@appfolio/react-gears",
"version": "7.9.0",
"version": "7.10.0-hook-form.8",
"description": "React-based version of Gears",
"author": "Appfolio, Inc.",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Form/FormRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const gearsInputs = {

type PropOrDefault<T extends {}, K extends PropertyKey, D = never> = K extends keyof T ? T[K] : D;
type ReactStrapInputTypes = NonNullable<React.ComponentProps<typeof Input>['type']>;
type InputTypes = ReactStrapInputTypes | keyof typeof gearsInputs;
export type InputTypes = ReactStrapInputTypes | keyof typeof gearsInputs;

function getInputByType<T extends InputTypes>(type: T) {
return (
Expand Down
202 changes: 138 additions & 64 deletions src/components/HookForm/Form.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from 'react';
import { SubmitHandler } from 'react-hook-form';
import { FormFeedback } from 'reactstrap';
import Button from '../Button/Button';
import FormGroup from '../Form/FormGroup';
import Label from '../Label/Label';
import Form from './Form';
import FormFeedback from './FormFeedback';
import FormLabelGroup from './FormLabelGroup';
import FormRow from './FormRow';
import Input from './Input';
import { SubmitHandler } from './types';

export default {
title: 'react-hook-form',
Expand All @@ -15,86 +17,158 @@ interface FormInputs {
email: string;
age: string;
select: string;
address: {
line1: string;
line2: string;
state: string;
zipCode: string;
};
}

export const LiveExample = () => {
const handleSubmit: SubmitHandler<FormInputs> = (formData) => {
export const FormWithValidations = () => {
const handleSubmit: SubmitHandler<FormInputs> = (formData, { setError }) => {
// make api calss
// if fails then call setError
setError('address.line1', {
message: 'something went wrong with line1',
});
console.log(formData);
};

return (
<Form onSubmit={handleSubmit} mode="onChange">
{({ formState: { errors, dirtyFields } }) => (
{({ reset, formState: { errors, dirtyFields } }) => {
console.log('render');
console.log(errors);
return (
<>
<div className="mb-3">
<Label for="email">Email</Label>
<FormFeedback>
<Input id="email" name="email" required="Can't be blank" />
</FormFeedback>

<FormFeedback name="email" />
</div>
<div className="mb-3">
<legend>Address</legend>
<Label for="line1">Address Line 1</Label>
<Input invalid={!!errors.address?.line1} id="line1" name="address.line1" />
<FormFeedback name="email" />
<Label for="line2">Address Line 2</Label>
<Input id="line2" name="address.addr2" />
<Label for="state">State</Label>
<Input id="state" name="address.state" />
<Label for="zipCode">Zip Code</Label>
<Input id="zipCode" name="address.zipCode" />
</div>
<div className="mb-3">
<Label for="age">Age</Label>
<Input
min={{ value: 1, message: 'Min is 1' }}
type="number"
invalid={!!errors.age}
id="age"
name="age"
/>
<FormFeedback invalid>{errors.age?.message}</FormFeedback>
</div>
<div className="mb-3">
<Label for="select">Select</Label>
<Input type="select" invalid={!!errors.select} id="select" name="select">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</Input>
<FormFeedback invalid>{errors.select?.message}</FormFeedback>
</div>
<div className="mb-3">
<FormLabelGroup inputId="select-multiple" label="Select multiple" stacked>
<Input type="select" id="select-multiple" name="selectMuliple" multiple>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</Input>
</FormLabelGroup>
</div>
<div className="mb-3">
<FormLabelGroup inputId="checkboxes" label="Check boxes" stacked>
<Input type="checkbox" id="checkbox1" name="checkboxes" value="Value 1" />
<Input type="checkbox" id="checkbox2" name="checkboxes" value="Value 2" />
</FormLabelGroup>
</div>
<div className="mb-3">
<legend>Radio Buttons</legend>
<FormGroup check>
<Input id="radio-option-1" name="radio" type="radio" value="radio-option-value-1" />{' '}
<Label check for="radio-option-1">
Option one is this and that—be sure to include why it‘s great
</Label>
</FormGroup>
<FormGroup check>
<Input id="radio-option-2" name="radio" type="radio" value="radio-option-value-2" />{' '}
<Label check for="radio-option-2">
Option two can be something else and selecting it will deselect option one
</Label>
</FormGroup>
</div>
<Button color="primary" type="submit">
Submit
</Button>
<Button type="button" onClick={() => reset()}>
Reset
</Button>
</>
);
}}
</Form>
);
};

interface FormValues {
email: string;
}

export const SimpleFormNoValidation = () => {
const handleSubmit: SubmitHandler<FormValues> = (formData) => {
console.log(formData);
};

return (
<Form onSubmit={handleSubmit}>
{({ formState: { isValid } }) => (
<>
<div className="mb-3">
<Label for="email">Email</Label>
<Input
valid={dirtyFields.email && !errors.email}
invalid={!!errors.email}
id="email"
name="email"
validate={(value) => value === 'email' || 'incorrect'}
/>
<FormFeedback invalid>{errors.email?.message}</FormFeedback>
<FormFeedback valid>Looks good!</FormFeedback>
</div>
<div className="mb-3">
<Label for="age">Age</Label>
<Input
min={{ value: 1, message: 'Min is 1' }}
type="number"
invalid={!!errors.age}
id="age"
name="age"
required="Can't be blank"
/>
<FormFeedback invalid>{errors.age?.message}</FormFeedback>
</div>
<div className="mb-3">
<Label for="select">Select</Label>
<Input type="select" invalid={!!errors.select} id="select" name="select">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</Input>
<FormFeedback invalid>{errors.select?.message}</FormFeedback>
</div>
<div className="mb-3">
<legend>Radio Buttons</legend>
<FormGroup check>
<Input id="radio-option-1" name="radio" type="radio" value="radio-option-value-1" />{' '}
<Label check for="radio-option-1">
Option one is this and that—be sure to include why it‘s great
</Label>
</FormGroup>
<FormGroup check>
<Input id="radio-option-2" name="radio" type="radio" value="radio-option-value-2" />{' '}
<Label check for="radio-option-2">
Option two can be something else and selecting it will deselect option one
</Label>
</FormGroup>
</div>
<Button type="submit">Submit</Button>
<FormRow name="test" type="month" />
<Button type="submit" disabled={!isValid}>
Submit
</Button>
</>
)}
</Form>
);
};

export const TestExample = () => {
const handleSubmit: SubmitHandler<FormInputs> = (formData) => {
export const FormDemo = () => {
const handleSubmit: SubmitHandler<FormValues> = (formData, { reset }) => {
console.log(formData);
reset();
};

return (
<Form onSubmit={handleSubmit} mode="onChange">
<div className="mb-3">
<Label for="email">Email</Label>
<Input id="email" name="email" />
<FormFeedback valid>Looks good!</FormFeedback>
</div>
<Form onSubmit={handleSubmit}>
<FormLabelGroup label="First Name">
<Input id="first-name" name="firstName" required="Can't be blank" />
</FormLabelGroup>
<FormLabelGroup label="Last Name">
<Input id="last-name" name="lastName" />
</FormLabelGroup>
<Button color="primary" type="submit">
Submit
</Button>
</Form>
);
};
57 changes: 48 additions & 9 deletions src/components/HookForm/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,68 @@
import React, { ReactNode } from 'react';
import React, { ReactNode, ComponentProps } from 'react';
import {
useForm,
FormProvider,
SubmitHandler,
SubmitHandler as HookFormSubmitHandler,
UseFormProps,
UseFormReturn,
FieldValues,
} from 'react-hook-form';
import GearsForm from '../Form/Form';
import { SubmitHandler } from './types';

interface FormProps<TFieldValues extends FieldValues> extends UseFormProps<TFieldValues> {
type BaseFormProps<TFieldValues extends FieldValues> = {
onSubmit: SubmitHandler<TFieldValues>;
children: ((useFormReturn: UseFormReturn<TFieldValues>) => ReactNode) | ReactNode;
}
};

type FormProps<TFieldValues extends FieldValues> = Omit<
ComponentProps<typeof GearsForm>,
keyof BaseFormProps<TFieldValues>
> &
UseFormProps<TFieldValues> &
BaseFormProps<TFieldValues>;

const Form = <TFieldValues extends FieldValues = FieldValues>({
const Form = <TFieldValues extends FieldValues = FieldValues, TContext = any>({
children,
onSubmit,
...useFormProps
action,
method,
onSubmit = () => undefined,
mode,
reValidateMode,
defaultValues,
resolver,
context,
criteriaMode,
shouldFocusError,
shouldUnregister,
shouldUseNativeValidation,
delayError,
...gearsFormProps
}: FormProps<TFieldValues>) => {
const useFormReturn = useForm<TFieldValues>(useFormProps);
const useFormReturn = useForm<TFieldValues, TContext>({
mode,
reValidateMode,
defaultValues,
resolver,
context,
criteriaMode,
shouldFocusError,
shouldUnregister,
shouldUseNativeValidation,
delayError,
});

const handleFormSubmit: HookFormSubmitHandler<TFieldValues> = async (formData, event) => {
return await onSubmit(formData, { ...useFormReturn }, event);
};

return (
<FormProvider {...useFormReturn}>
<GearsForm noValidate onSubmit={useFormReturn.handleSubmit(onSubmit)}>
<GearsForm
noValidate
onSubmit={useFormReturn.handleSubmit(handleFormSubmit)}
{...gearsFormProps}
>
{typeof children === 'function' ? children(useFormReturn) : children}
</GearsForm>
</FormProvider>
Expand Down
Loading

0 comments on commit ce95b0c

Please # to comment.