modular-forms
modular-forms copied to clipboard
qwik, routeloader vs signal store as loader for useForm
On https://modularforms.dev/qwik/guides/create-your-form loader is suggested to be routeLoader, but technically any store (using signals) should work, correct?
Great library btw. I first started doing things manually but soon realised it is better in the long run to use this approach. Thank you.
Hey, thanks a lot! Yes that is correct. You can also just pass { value: { /* initial values here */ } } to loader. I am aware that this is currently not optimal for static values and I am in the process of improving this. What do you think about the following solution?
const [loginForm, { Form, Field }] = useForm<LoginForm>({
loader: staticValues({ email: '', password: '' }),
});
With the name, I am not sure yet. Instead of staticValues it could also be staticLoader or useStaticLoader.
Alternatively it is also possible to use useSignal. However, I find this somehow a bit confusing, since it is basically not a reactive state.
const [loginForm, { Form, Field }] = useForm<LoginForm>({
loader: useSignal({ email: '', password: '' }),
});
Hmm well in my case I was simply looking for a way to set initial values on a form. since it is a form that is not directly attached to a route but instead receives data from a parent component, I initialised a loader "store":
const loader = useSignal<AddressFormType>({
firstName: address?.firstName || '',
lastName: address?.lastName || '',
streetAddress1: address?.streetAddress1 || '',
streetAddress2: address?.streetAddress2 || '',
city: address?.city || '',
postalCode: address?.postalCode || '',
phone: address?.phone || '',
});
and then attached this to useForm. I don't see a big issue with doing it this way. A signal is a signal, right? It seems to work, anyways. ;-)
Of course, for DX, you could provide an immediate handler and name it something smart like "initialValueLoader<AddressFormType>({...})" but it would essentially be syntactical sugar. I was just wondering wether this has some kind of side effect that I am not aware of.
edit: Ah, fired the comment too soon - so this essentially means I could save myself a signal here and just drop in the data as you described above.
Later down I track values that I use to update the form contents dynamically via context and for that I set them directly on the form using
useTask$(async ({ track }) => {
const firstName = track(() => storeContext.profile?.firstName);
const lastName = track(() => storeContext.profile?.lastName);
addressForm.internal.fields.firstName!.value = firstName;
addressForm.internal.fields.lastName!.value = lastName;
});
Does the fact that the object is named internal imply that it should not be called directly?
Thank you for your further feedback. You misunderstood me, I meant the following:
const [loginForm, { Form, Field }] = useForm<LoginForm>({
loader: { value: { email: '', password: '' } },
});
Correct, internal should not be used, as there is a risk that the state becomes inconsistent. Instead, the various methods should be used for modifications.
You are right, a separate function like staticValues would only be syntax sugar, but in the long run we should choose a uniform procedure and document it that way. Alternatively I could add initialValues next to loader, where the initial data can be passed directly.
Here another tip: To change the initial values afterwards, the reset method should be used.
For simplicities sake, I would prefer to set a form value on an existing form object.
Let's say I setup a form like this:
const [addressForm, { Form, Field }] = useForm<AddressFormType>({
loader,
});
To set a form field's value later it would be much more convenient to use something like
addressForm.update('fieldname', value);
and let the form handle the rest by itself. Right now it is not entirely clear to me how I should set up a form if I need to change values dynamically. Do I need to initialise using useFormStore instead of useForm? Or a separate instance? how do I get my values into useFormStore? It feels a little bit unintuitive.
The library is built with a modular design. Thus the bundle size is small and you can decide yourself which functionality you need. You can find more information here.
The store of your form is therefore the core, because it holds all the information. To make changes to it, you use methods like setValue. You can find more about this here.
setValue(addressForm, 'fieldname', 'New value');
Thank you for elaborating. I appreciate it.
@fabian-hiller could we just make the loader optional or is a default zero value always required for it to work?
Currently it is required. But you're right, there are ways around it. I'm planning a major update in January that will simplify the API and increase stability.