Skip to content

Instantly share code, notes, and snippets.

@musou1500
Last active January 17, 2022 14:06
Show Gist options
  • Save musou1500/bd9695d1d3a203822277b5fba3ed56a4 to your computer and use it in GitHub Desktop.
Save musou1500/bd9695d1d3a203822277b5fba3ed56a4 to your computer and use it in GitHub Desktop.
design of type-safe form library
import React from "react";
// lib.ts
type FieldValue<L, R> = { isValid: false; value: L; } | { isValid: true; value: R };
type FieldApi<L, R> = {
setValue: (value: FieldValue<L, R>) => void;
getValue: () => FieldValue<L, R>;
};
type FComponent<L, R> = React.VFC<{ field: FieldApi<L, R> }>;
type FItem<K extends string, L, R> = readonly [K, FComponent<L, R>];
type FItemToObject<T> = T extends FItem<infer K, infer L, infer R>
? Nested<K, (FieldValue<L, R> & { isValid: true })["value"]>
: never;
type Infer<
T extends readonly unknown[]
> = T extends readonly [infer F, ...infer R]
? FItemToObject<F> & Infer<R>
: {};
type Nested<S extends string, V> = S extends `${infer Fst}.${infer Rest}`
? { [key in Fst]: Nested<Rest, V> }
: { [key in S]: V };
declare function createField<L, R = L>(cmp: FComponent<L, R>): FComponent<L, R>;
declare function createForm<T extends readonly unknown[]>(fields: T): React.VFC<{ onSubmit: (value: Infer<T>) => void }>;
// fields.ts
const textField = createField<string>(({ field }) => {
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) =>
field.setValue({ isValid: e.target.value.length > 0, value: e.target.value });
const { value } = field.getValue();
return <input type="text" value={value} onChange={onChange} />
});
const intField = createField<number>(({ field }) => {
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const intValue = parseInt(e.target.value, 10);
field.setValue({ isValid: isNaN(intValue), value: intValue });
};
const { value } = field.getValue();
return <input type="text" value={value} onChange={onChange} />
});
// user-form-page.tsx
// React.VFC<{
// onSubmit: (value: {
// user: {
// name: string;
// age: number;
// };
// }) => void;
// }>
const UserForm = createForm([
// key, component
["user.name", textField],
["user.age", intField]
] as const);
const UserPage = () => {
return <UserForm onSubmit={(value) => console.log(value)}/>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment