felte
felte copied to clipboard
Initial data with optional types becomes non-optional
Is your feature request related to a problem? Please describe.
While it makes sense that the form's $data
store gets turned into non-optional types, this isn't always so great especially for values that are handled outside of form elements. For example I have a form where I can edit a user, and the user can upload an avatar or clear out the avatar. The avatar is optional, because it can be cleared by sending null
to the server (not an empty string). See this example:
<script lang="ts">
type CreateOrUpdateUser = {
name: string;
bio: string;
avatar?: string;
};
export let user: CreateOrUpdateUser;
const { form, data, isValid, isSubmitting } = createForm({
initialValues: user,
onSubmit: values => {
console.log(values);
},
});
function fileUploaded(file: string) {
$data.avatar = file;
}
function clearAvatar() {
$data.avatar = undefined;
}
</script>
<form class="form" use:form>
// A mixture of input fields for `name` and `bio`, and custom components that call `fileUploaded` and `clearAvatar`
</form>
I am getting a TypeScript error here, because $data.avatar
has becomes string
, rather than string | undefined
.
Describe the solution you'd like If I declare a field as optional, I would want the form library to respect that type and internally deal with it. It shouldn't modify the type I have chosen for a good reason.
Describe alternatives you've considered
I could use empty strings and then in the end use transformations to turn empty strings into undefined
. But that adds boilerplate which wouldn't be necessary if felte wouldn't modify my types. But much worse, adding a transform
function completely gets rid of all types altogether, which really isn't great.
For me this is a dealbreaker to using felte, sadly.
I agree it would be nice if this was a simple flag in the createForm options that would handle empty strings as undefined.
my workaround right now is:
const felteTransformEmptyStringToUndefined = (v: any) => {
for (const key in v) {
const value = v[key]
if (value === "") v[key] = undefined;
if (typeof value === "object") felteTransformEmptyStringToUndefined(value)
}
return v;
}
const { form, errors, isSubmitting, data } = createForm({
onSubmit: handleSubmit,
transform: felteTransformEmptyStringToUndefined,
});
@kevinrenskers is there another Svelte Forms Libary you are using which has this functionallity out of the box?
I'm not using a library but this bit of code:
export function copy<T>(value: T): T {
return JSON.parse(JSON.stringify(value));
}
type FormConfig<T> = {
initialValues: T;
required?: Array<keyof T>;
validate?: (values: T) => boolean;
onError?: (error: unknown) => void;
onSubmit: (values: T) => Promise<void> | undefined;
};
export function createForm<T>(config: FormConfig<T>) {
const values = copy(config.initialValues);
const required = config.required || [];
const form = writable(values);
const isSubmitting = writable(false);
const isValid = derived(form, $form => {
return formIsValid($form);
});
const onError = config.onError || handleError;
function formIsValid(values: T) {
let valid = true;
required.forEach(field => {
const value = values[field];
if (typeof value === "undefined" || (typeof value === "string" && value === "")) {
valid = false;
}
});
if (!valid) {
return false;
}
if (config.validate) {
valid = config.validate(values);
}
return valid;
}
function handleError(error: unknown) {
// Custom logic to show the errors
}
function handleSubmit(ev: Event & { currentTarget: EventTarget & HTMLFormElement }) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
if (!formIsValid(values)) {
// Invalid form
return;
}
return Promise.resolve()
.then(() => isSubmitting.set(true))
.then(() => config.onSubmit(values))
.finally(() => isSubmitting.set(false))
.catch(onError);
}
return {
form,
isValid,
isSubmitting,
handleSubmit,
};
}