Skip to content

Instantly share code, notes, and snippets.

@AlexGeb
Created September 11, 2024 09:37
Show Gist options
  • Select an option

  • Save AlexGeb/c9682ec6425301a858c6a7c8c288ebb0 to your computer and use it in GitHub Desktop.

Select an option

Save AlexGeb/c9682ec6425301a858c6a7c8c288ebb0 to your computer and use it in GitHub Desktop.
Renders a form given an Effect schema
import type { Schema } from '@effect/schema';
import { AST } from '@effect/schema';
import { effectTsResolver } from '@hookform/resolvers/effect-ts';
import { Button } from '@inato/ui';
import { Match, Option } from 'effect';
import type {
FieldValues,
SubmitErrorHandler,
SubmitHandler,
} from 'react-hook-form';
import { FormProvider, useForm, useFormContext } from 'react-hook-form';
import { BooleanRadioYesNo } from '../../../EnrollmentPlan/shared';
import { TextInputFormField } from '@components/TextInputFormField';
export function FormRenderer<T extends FieldValues>({
schema,
}: {
schema: Schema.Schema<T, any>;
}) {
const formMethods = useForm<T>({
mode: 'onBlur',
resolver: effectTsResolver(schema),
});
const { handleSubmit } = formMethods;
const onSubmit: SubmitHandler<T> = data => console.log(data);
const onSubmitError: SubmitErrorHandler<T> = errors => console.error(errors);
return (
<form
onSubmit={handleSubmit(onSubmit, onSubmitError)}
className="flex flex-col gap-m"
>
<FormProvider {...formMethods}>
<ASTRenderer ast={schema.ast} />
</FormProvider>
<div>
<Button type="submit">save</Button>
</div>
</form>
);
}
const ASTRenderer: React.FunctionComponent<{
ast: AST.AST | AST.PropertySignature;
fieldName?: string;
}> = ({ ast, fieldName = '' }) => {
const { control } = useFormContext();
if (ast instanceof AST.PropertySignature) {
return (
<ASTRenderer
key={ast.name.toString()}
ast={ast.type}
fieldName={`${fieldName}.${ast.name.toString()}`}
/>
);
}
const matcher = Match.type<AST.AST>().pipe(
Match.tags({
TypeLiteral: ast =>
ast.propertySignatures.map(ast => (
<ASTRenderer
key={ast.name.toString()}
ast={ast}
fieldName={fieldName}
/>
)),
BooleanKeyword: ast => (
<BooleanRadioYesNo
groupLabel={AST.getTitleAnnotation(ast).pipe(Option.getOrNull)}
name={fieldName}
control={control}
/>
),
StringKeyword: ast => (
<TextInputFormField
label={AST.getTitleAnnotation(ast).pipe(Option.getOrNull)}
name={fieldName}
control={control}
/>
),
Union: ast =>
ast.types.map(ast => (
<ASTRenderer key={ast._tag} ast={ast} fieldName={fieldName} />
)),
}),
Match.when(Match.any, () => null),
Match.exhaustive,
);
return matcher(ast);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment