Skip to content

Instantly share code, notes, and snippets.

@reidev275
Created July 10, 2019 17:52
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 reidev275/ef831467516f0943a478f01b162dc0a0 to your computer and use it in GitHub Desktop.
Save reidev275/ef831467516f0943a478f01b162dc0a0 to your computer and use it in GitHub Desktop.
import React, { Component } from "react";
import { FormModel, Form } from "./Form";
type Person = {
firstName: string;
lastName: string;
birthdate: Date | undefined;
gender: "Male" | "Female" | "Trans" | "Other" | "";
children: number;
};
const formModel: FormModel<Person> = {
firstName: {
title: "First Name",
type: "text",
value: "",
validation: a => a !== null && a.length > 0
},
lastName: {
title: "Last Name",
type: "text",
value: "Evans"
},
birthdate: {
title: "Birthdate",
type: "date",
value: undefined,
validation: a => a != undefined
},
gender: {
title: "Gender",
type: "select",
value: "Male",
validation: a => a != "",
options: [
{ key: "", label: "" },
{ key: "Male", label: "Male" },
{ key: "Female", label: "Female" },
{ key: "Trans", label: "Trans" },
{ key: "Other", label: "Other" }
]
},
children: {
title: "Number of Children",
type: "number",
value: 3,
validation: a => a < 10 && a >= 0
}
};
class App extends Component {
render() {
return (
<div className="App">
<Form model={formModel} />
</div>
);
}
}
export default App;
import React, { Component } from "react";
//prettier-ignore
type InputType<A extends any>
= A extends string ? "text"
: A extends number ? "number"
: A extends Date ? "date"
: "text";
type Option<A> = {
key: A;
label: string;
};
type Input<A> = {
title: string;
value: A;
validation?: (a: A) => boolean;
} & ({ type: "select"; options: Option<A>[] } | { type: InputType<A> });
export type FormModel<A> = { [P in keyof A]: Input<A[P]> };
type FormState<T> = { form: FormModel<T> } & (
| { status: "unsubmitted"; validationFailures: { [K in keyof T]?: string } }
| { status: "processing"; message: string }
| { status: "errored"; errors: { [K in keyof T]: string }; message: string }
| { status: "complete"; message: string });
interface Props<A> {
model: FormModel<A>;
}
export class Form<A> extends Component<Props<A>, FormState<A>> {
constructor(props: Props<A>) {
super(props);
this.state = {
form: props.model,
status: "unsubmitted",
validationFailures: {}
};
this.inputChange = this.inputChange.bind(this);
this.selectChange = this.selectChange.bind(this);
this.submit = this.submit.bind(this);
}
selectChange(event: React.ChangeEvent<HTMLSelectElement>) {
const field = event.target.name as (keyof A);
const value = event.target.value as unknown;
const property = this.state.form[field];
const state = {
...this.state,
form: {
...(this.state.form as any),
[field]: Object.assign({}, property, { value })
}
};
this.setState(state);
}
inputChange(event: React.ChangeEvent<HTMLInputElement>) {
const field = event.target.name as (keyof A);
const value = event.target.value as unknown;
const property = this.state.form[field];
const state = {
...this.state,
form: {
...(this.state.form as any),
[field]: Object.assign({}, property, { value })
}
};
this.setState(state);
}
submit(e: any): void {}
render() {
const inputs = Object.keys(this.props.model) as (keyof FormModel<A>)[];
switch (this.state.status) {
}
return (
<form onSubmit={this.submit}>
{inputs.map((x: keyof A) => {
const property = this.state.form[x];
const validation = property.validation || (x => true);
const isValid = validation(property.value);
return (
<div key={x as string}>
<label htmlFor={x as string}>{property.title}</label>
{property.type === "select" ? (
<select
name={x as string}
className={isValid ? "" : "error"}
onChange={this.selectChange}
>
{(property as any).options.map((o: Option<A>, i: number) => (
<option key={i} value={o.key as any}>
{o.label}
</option>
))}
</select>
) : (
<input
className={isValid ? "" : "error"}
name={x as string}
type={property.type as string}
value={property.value as any}
onChange={this.inputChange}
/>
)}
</div>
);
})}
<button>Submit</button>
</form>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment