fabian-hiller / valibot

The modular and type safe schema library for validating structural data 🤖
https://valibot.dev
MIT License
6k stars 186 forks source link

Predefined keys for `ObjectSchema` #619

Closed migueldamota closed 4 months ago

migueldamota commented 4 months ago

Hi there, I have a question about ObjectSchema. Is there a way to predefine keys? Because I want to build a custom useForm hook. However, I can't find a way to type check the keys with object(). I already tried using a ObjectSchema with a Record and it worked but now I wanted to update it with the new pipe functionality and I am struggling with updating it 🥲

export default function Form() {
    const form = useForm({
        fields: {
            name: "",
        },
        validation: v.object({
            name: v.string(),
        }),
        onSubmit(values) {
            console.log(values);
        },
    });
}

If you have an idea on how I can do this or have any questions, please let me know.

fabian-hiller commented 4 months ago

I am sure there is a solution to this problem. Unfortunately, I did not get the full context. Can you provide more details and example code? Maybe provide me with your previous code and I will try to rewrite it to use the new pipe method.

migueldamota commented 4 months ago

Thanks for your response. Sure.

Here are some of the useForm types which are mainly used for validation:

// this is based on valibot@0.30.0
import type * as v from "valibot";

type ValidationSchema<V> = v.ObjectSchema<Record<keyof V, v.BaseSchema>>;

interface FormConfig<Values> {
    fields: Values;
    validation?: ValidationSchema<Values>;

    // other stuff...
}

export function useForm<V extends DefaultForm = DefaultForm>({ fields, validation, ... }: FormConfig<V>) {
    // logic...
}

Values is an object which defines the fields you want to use in the form. validation should hold an object schema which keys are of Values and just a pure schema like string() or a pipe.

Here is another example code:

import * as v from "valibot";

const form = useForm({
    fields: {
        name: "",
        email: "",
        password: "",
    },
    // should have autocompletion for keys: name, email and password
    validation: v.object({
        name: v.string(),
        email: v.pipe(v.string(), v.email()),
        // should error because `password` is not defined
    }),
});

I hope everything is clear 😄

fabian-hiller commented 4 months ago

To be honest, I don't know why TypeScript complains when using ObjectSchema as a generic, but I found two workarounds:

import * as v from 'valibot';

type SchemaEntries<TValues> = {
  [TKey in keyof TValues]: v.GenericSchema<TValues[TKey], unknown>;
};

interface FormConfig<TValues, TEntries extends SchemaEntries<TValues>> {
  fields: TValues;
  validation?: TEntries;
  // other stuff...
}

function useForm<TValues, TEntries extends SchemaEntries<TValues>>(
  config: FormConfig<TValues, TEntries>
) {
  const schema = config.validation && v.object(config.validation);
  // more code here...
}

const form = useForm({
  fields: {
    foo: 'foo',
    bar: 42,
  },
  validation: {
    foo: v.string(),
    bar: v.number(),
  },
});
import * as v from 'valibot';

type SchemaEntries<TValues> = {
  [TKey in keyof TValues]: v.GenericSchema<TValues[TKey], unknown>;
};

interface FormConfig<TValues, TEntries extends SchemaEntries<TValues>> {
  fields: TValues;
  validation?: v.ObjectSchema<
    TEntries,
    v.ErrorMessage<v.ObjectIssue> | undefined
  >;
  // other stuff...
}

function useForm<TValues, TEntries extends SchemaEntries<TValues>>(
  config: FormConfig<TValues, TEntries>
) {
  // more code here...
}

const form = useForm({
  fields: {
    foo: 'foo',
    bar: 42,
  },
  validation: v.object({
    foo: v.string(),
    bar: v.number(),
  }),
});
migueldamota commented 4 months ago

It's working! Thank you so much 🙏