Skip to content

Commit

Permalink
feat(standard-schema): automatically infer values from schema
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisre committed Feb 6, 2025
1 parent ea150d5 commit b02d257
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 25 deletions.
38 changes: 32 additions & 6 deletions standard-schema/src/__tests__/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ const schema = type({

type FormData = typeof schema.infer & { unusedProperty: string };

interface Props {
onSubmit: (data: FormData) => void;
}

function TestComponent({ onSubmit }: Props) {
function TestComponent({
onSubmit,
}: {
onSubmit: (data: typeof schema.infer) => void;
}) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
} = useForm({
resolver: standardSchemaResolver(schema), // Useful to check TypeScript regressions
});

Expand Down Expand Up @@ -54,3 +54,29 @@ test("form's validation with arkType and TypeScript's integration", async () =>
).toBeInTheDocument();
expect(handleSubmit).not.toHaveBeenCalled();
});

export function TestComponentManualType({
onSubmit,
}: {
onSubmit: (data: FormData) => void;
}) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<typeof schema.infer, undefined, FormData>({
resolver: standardSchemaResolver(schema), // Useful to check TypeScript regressions
});

return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('username')} />
{errors.username && <span role="alert">{errors.username.message}</span>}

<input {...register('password')} />
{errors.password && <span role="alert">{errors.password.message}</span>}

<button type="submit">submit</button>
</form>
);
}
2 changes: 1 addition & 1 deletion standard-schema/src/__tests__/__fixtures__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const invalidData = {
birthYear: 'birthYear',
like: [{ id: 'z' }],
url: 'abc',
};
} as any as typeof schema.infer;

export const fields: Record<InternalFieldName, Field['_f']> = {
username: {
Expand Down
1 change: 0 additions & 1 deletion standard-schema/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './standard-schema';
export * from './types';
19 changes: 12 additions & 7 deletions standard-schema/src/standard-schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
import { StandardSchemaV1 } from '@standard-schema/spec';
import { FieldError } from 'react-hook-form';
import type { Resolver } from './types';
import { FieldError, FieldValues, Resolver } from 'react-hook-form';

const parseIssues = (issues: readonly StandardSchemaV1.Issue[]) => {
function parseIssues(issues: readonly StandardSchemaV1.Issue[]) {
const errors: Record<string, FieldError> = {};

for (let i = 0; i < issues.length; i++) {
Expand All @@ -18,10 +17,15 @@ const parseIssues = (issues: readonly StandardSchemaV1.Issue[]) => {
}

return errors;
};

export const standardSchemaResolver: Resolver =
(schema) => async (values, _, options) => {
}

export function standardSchemaResolver<
TFieldValues extends FieldValues,
Schema extends StandardSchemaV1<TFieldValues, any>,
>(
schema: Schema,
): Resolver<NonNullable<(typeof schema)['~standard']['types']>['output']> {
return async (values: TFieldValues, _, options) => {
let result = schema['~standard'].validate(values);
if (result instanceof Promise) {
result = await result;
Expand All @@ -43,3 +47,4 @@ export const standardSchemaResolver: Resolver =
errors: {},
};
};
}
10 changes: 0 additions & 10 deletions standard-schema/src/types.ts

This file was deleted.

0 comments on commit b02d257

Please sign in to comment.