ciscoheat / sveltekit-superforms

Making SvelteKit forms a pleasure to use!
https://superforms.rocks
MIT License
2.13k stars 63 forks source link

ArkType without default values #462

Closed joleeee closed 1 month ago

joleeee commented 1 month ago

Description I think, with the current way that defaults are required, it is not (easily?) possible to have a form where certain fields must have a value, but shall remain unfilled.

I have a form where among other things I have a date field. This is required, but using superforms + arktype, I have to specify a default. I don't want there to be a default...

export const flightSchema = type({
    date: "string",
})

export type FlightSchema = typeof flightSchema;

export const flightSchemaDefaults = {
    // date: undefined // not possible
    date: "2024-01-01",
}

export async function load({ params, cookies, depends }) {
    const flightForm = await superValidate(arktype(flightSchema, { defaults: flightFormDefaults }))
    return { flightForm }
}

The best workaround I have come up with is to have a separate schema which has the | null, and then use this on the client, but validate server side with the proper one (no | null). Is there a proper way to achieve what I'm asking for here yet?

FAQ says:

The libraries in the list that requires defaults don’t have full introspection capabilities (yet), in which case you need to supply the default values for the form data as an option:

I cannot find a tracking issue here or in the arktype repo for this functionality.

joleeee commented 1 month ago

Whats strange to me is that a type string>3 can have default "a", which is definitely not string>3, but it cannot be null even though null is also not string>3...

I tried working around this by creating a null type but narrowing it down to not be null. Unfortunately arktype is a bit too smart for this workaround:

const notNull = type("null").narrow((value, ctx) => {
    return ctx.mustBe("not null :)")
}
// ParseError: Intersection of null and $ark.fn8 results in an unsatisfiable type
ciscoheat commented 1 month ago

Default values don't have to be valid, they only have to be the correct type for the schema data.

The convenient way to have an empty field for a non-nullable property is to use a proxy: https://superforms.rocks/api#proxy-objects

joleeee commented 1 month ago

Yes so you can use the proxies to have null instead of "" for instance, but there is no way to avoid putting a | null or | undefined for every field in the schema?

ciscoheat commented 1 month ago

There is no need for that when you use a proxy.

joleeee commented 1 month ago

Sorry but I don't see how 😅

In the example from above, what do i put in the default? I don't want to set a default. I don't see how the proxy fixes this, as that is for the client side?

export const flightSchema = type({
    date: "string",
})

export const flightSchemaDefaults = {
    date: // what do i put here
}
ciscoheat commented 1 month ago

You can try new Date('') to get an invalid date, but I'm not sure how the date input (or the proxy) reacts on that.

ciscoheat commented 1 month ago

If your date is a string though, you should be able to use an empty string.

joleeee commented 1 month ago

I guess it works for strings that way, because "" is kinda invalid. What about if instead of it being a date, it is a number. Maybe you can use NaN or something. Still, "" is a valid value, and NaN is a valid number, so the schema will be able to submit, except if you manually add a .narrow() or proxy to "" -> null (only client side, can still craft a response with "").

But what about some other type where there is no inherit "invalid" value. I have a bigger example here which is real:

export const airport = type({
    id: "number",
    icao: "string",
    iata: "string",
    type: "string",
    lat: "number",
    lon: "number",
    city: "string",
    name: "string",
    country: "string",
    loc_id: "number",
});

export const flight = type({
    date: "string",
    from: airport,
    // 15 fields removed for brievety
})

export const flightDefaults = {
    date: "",
    from: // what here
}

The user can select an airport and all the fields get filled. But what about when no airport is selected yet? I cannot do from: null because null is not airport, and I don't want to do airport.or(type("null")) because I don't want you to be able to submit with a null value. Do i just have to manually setError if it is null? It feels so wrong.

ciscoheat commented 1 month ago

Then you need to constrain the fields so the default value won't be valid. For example, city as just "string" will allow an empty string, a string with only spaces, only punctuation, etc.

ciscoheat commented 1 month ago

In case you haven't seen it, here are the default values added to non-optional fields without a default value: https://superforms.rocks/default-values (except for adapters like Arktype, since the default values must be explicit.)

joleeee commented 1 month ago

This kinda works

export type Airport = typeof airport.t;
export const flightDefaults = {
    date: "",
    from: null as unknown as Airport,
}
joleeee commented 1 month ago

I think my thoughts can be summed up in that I think there should be a way to have default values which are invalid (and different types) than the fields they are going to be in.

ciscoheat commented 1 month ago

Invalid default values with the same type is no problem (empty string with validators that doesn't allow that), but different types aren't possible, since that would break type safety. You need to cast it yourself with as unknown (or use a proxy on the client).