modular-forms
modular-forms copied to clipboard
[Question] Can it run with SSR?
It would be awsome if it runs and validates on the server side too...
Yes, SSR works. Since our only dependency is SolidJS, you can pre-render the form on the server. However, validation only works on the client so far. Whether it works with streaming, I'm not sure. On the server I mainly use Zod to validate the incoming data.
Is your question also aimed at integrating SolidStart's createRouteAction
API, so that if in doubt, the app will work without JavaScript in the browser?
Thanks! That was the precise question!
Currently i'm using remixjs, but SolidStart is great! And i'm starting to migrate. But i'm concerned 'bout the ssr validation on forms.
Since the documentation for SolidStart was not yet ready at the time of the development of this library, I could unfortunately not yet consider createRouteAction
. But I will inform myself in the next weeks and check if an integration is possible and reasonable.
Did you also use a form library with Remix? How did the validation work there?
In Remix, I would use a Fetcher which is a component that self-updates fetching data from routes and re-renders the updates on itself.
Thank you for the information. I will give you a first assessment by next week.
Hi, I am trying to use Modular Forms with Solid Start (not sure this is the best place to ask - noob here), I am using the example snippet from https://modularforms.dev/guides/validate-your-fields, which is working fine on my Solid (not Start) App.
The component is showing correctly, but when I type text in the inputs, the validation acts as if I typed nothing.
reactivity.ts
import { createForm } from "@modular-forms/solid"
type LoginForm = {
email: string
password: string
}
export const createLogin = () => {
const loginForm = createForm<LoginForm>()
return loginForm
}
component.tsx
import { createLogin } from "./reactivity"
interface Props extends ReturnType<typeof createLogin> {}
const Login: Component<Props> = (props) => {
return (
<Form of={props}...>
<Field of={props}...>{(field) => (<input>...</input>)}</Field>
...
</Form>)}
export default Login
app index.tsx
import Login, { createLogin } from "@components/login"
export default function () {
const login = createLogin()
return (
<>
<Login {...login} />
</>
)
}
Thanks for your help and sorry if the question is misplaced.
Cheers
Hi @thibaudgrosjean. The problem could be that you are using the spread operator at <Login {...login} />
. This can break the fine-graind reacktivity. It doesn't matter if you use SolidJS with or without SolidStart. For example, our documentation is written with SolidStart.
If the problem persists, I recommend putting the code into one component and then piece by piece splitting it up again until you find the problem. Feel free to give me an update here. I am happy to help.
You can also join the SolidJS Discord server and contact me there: https://discord.com/invite/solidjs
Hey Fabian, I must be doing something wrong (I'll check this spread thingy out), I have tried putting the code in a single file but still got the same output. I will study your doc source code, I might learn a few things:D Thanks for the heads up !
Hi, so I was having the issue on the bat template (https://github.com/olgam4/bat) but using the Solid Start templates (tried both with and without SSR) it is indeed working fine, I guess it has something to do with the bat template config, anyway, have a nice day:)
In the end, this library only uses SolidJS and the browser API. There should only be problems if the required JavaScript code is not passed to the browser. For example because it is an MPA instead of an SPA. If you are interested, you can provide me a GitHub repository with your code and I'll take a closer look.
@EuSouVoce I have looked at createRouteAction
and createServerAction$
from SolidStart. An integration seems to me possible without big changes. However, I need to go deeper into SolidStart for this.
Currently, my idea is to add a createActionForm
and createServerForm$
primitive to Modular Forms that internally use createRouteAction
and createServerAction$
from SolidStart. The code in the JSX section should not be affected and createForm
, createActionForm
and createServerForm$
should be able to be interchanged as desired. This means the API will remain the same according to my current estimation.
The only difference to the current implementation will be, that the submit function will have to be passed directly to createActionForm
or createServerForm$
. Also, it will be necessary to add optional schema validation, e.g. using Zod, which can also be run on the server. I plan to add the schema validation in the next days.
The first step is done. With v0.9 it is now possible to validate the form with a Zod schema. You can find more about this here.
AWSOME!!!!
Is there progress on this? I want to use modular forms for a site I am building and would need this feature.
I answered this question in the FAQ of the website a few hours ago. Basically you can use Modular Forms with SolidStart. I'm doing this myself on a larger project. The integration of SolidStart actions is only interesting if your forms need to work without JavaScript in the browser or you are developing an MPA and don't want to send JavaScript to the browser at all. Otherwise, it makes no difference to your end users.
At this point, I can only make assumptions about the final implementation. However, if everything goes according to my plan, it should be possible to implement Modular Forms as described in the current documentation, and once the new APIs with the SolidStart actions are available, you simply swap them out and make a few small changes.
The integration of SolidStart actions is only interesting if your forms need to work without JavaScript in the browser
Exactly that ist the usecase. I need it to work without client JS at all. So the form has to be rendered with values and error messages server side.
However the project will hopefully go life in one or two months. Will it be ready then?
Yes, that's the plan. However, I need to dig deeper into how the SolidStart actions work. Especially how to get the error messages into the HTML. Also I'm still unsure if I can simply build on createServerAction$
for example, as I've made the experience that server$
only works in the routes
directory. However, I could be wrong with this. I'll try to give you an update here by the end of the week.
There is the FormError class of solid-start, which is designed for these usecases. But errors are redirected to the referrer with the error as URL parameter. For big forms this might be a problem, as the maximum URL length is about 4000 characters afaik, which might be too less for values and error messages.
I can only think of a session as alternative for holding the error messages and values for a redirect. But that might be difficult for a form library...
I dug a little deeper and also investigated what approach Remix has. My current guess, as you mentioned, is that with SolidStart the entire form state needs to be added to the URL as a parameter or a cookie so that server-side the page can be re-rendered with the previous form values and optionally the error messages. If there is a limit on the URL or cookie length, however, that seems too error-prone to me. I will ask for advice on this in the SolidJS Discord.
During my tests I also encountered other possible problems. For example, if I use createRouteAction
and disable JavaScript in the browser and submit the form, only the page reloads and nothing else happens. In this case, I expected the data to be processed automatically on the server side. Another problem I encountered is that createServerAction$
behaves like createRouteAction
and is executed client-side when defined outside the route in which it is used. This causes problems for us as a library, as it means that we cannot currently build on createServerAction$
in our code.
So in summary, I would say that because of the current problems, it is not possible to predict when we will be able to offer a satisfactory solution for progressively enhanced forms. All currently possible implementations seem to me to involve too much boilerplate code for the users of the library. Below is an example:
import {
createForm,
Field,
handleActionSubmit,
handleFormAction,
zodForm,
} from "@modular-forms/solid";
import { useSearchParams } from "@solidjs/router";
import { createServerAction$ } from "solid-start/server";
import { z } from "zod";
import { TextInput, Button } from "~/components";
const loginSchema = z.object({
email: z
.string()
.min(1, "Please enter your email.")
.email("The email address is badly formatted."),
password: z
.string()
.min(1, "Please enter your password.")
.min(8, "You password must have 8 characters or more."),
});
export default function LoginRoute() {
const [searchParams] = useSearchParams();
const loginForm = createForm<z.infer<typeof loginSchema>>({
validate: zodForm(loginSchema),
state: searchParams.formState,
});
const [_, loginAction] = createServerAction$(async (formData: FormData) =>
handleFormAction(loginForm, formData, async (values) => {
// Your code
})
);
return (
<loginAction.Form
onSubmit={handleActionSubmit(loginForm, loginAction)}
noValidate
>
<Field of={loginForm} name="email">
{(field) => (
<TextInput
{...field.props}
type="email"
label="Email"
value={field.value}
error={field.error}
required
/>
)}
</Field>
<Field of={loginForm} name="password">
{(field) => (
<TextInput
{...field.props}
type="password"
label="Password"
value={field.value}
error={field.error}
required
/>
)}
</Field>
<Button type="submit">Login</Button>
</loginAction.Form>
);
}
The ideal implementation, on the other hand, would look something like this:
import { createServerForm$, Field, zodForm } from "@modular-forms/solid";
import { z } from "zod";
import { TextInput, Button } from "~/components";
const loginSchema = z.object({
email: z
.string()
.min(1, "Please enter your email.")
.email("The email address is badly formatted."),
password: z
.string()
.min(1, "Please enter your password.")
.min(8, "You password must have 8 characters or more."),
});
export default function LoginRoute() {
const [loginForm, Form] = createServerForm$<z.infer<typeof loginSchema>>({
validate: zodForm(loginSchema),
onSubmit: async (values) => {
// Your code
},
});
return (
<Form>
<Field of={loginForm} name="email">
{(field) => (
<TextInput
{...field.props}
type="email"
label="Email"
value={field.value}
error={field.error}
required
/>
)}
</Field>
<Field of={loginForm} name="password">
{(field) => (
<TextInput
{...field.props}
type="password"
label="Password"
value={field.value}
error={field.error}
required
/>
)}
</Field>
<Button type="submit">Login</Button>
</Form>
);
}
I must say, that the more I dig into this, the more problems I see with validation in browsers with JS disabled.
The client side validation is currently done with that object of type FieldValues
. But server side validation would have to be done for FormData
, as this is the type of data we get from a POST
request from a form.
There are a few problems with this:
- The objects do not have the same shape. This is quite difficult, especially with checkboxes, as they are an array on client side by default, even if there is only one (I may be wrong here), but not on the server side.
- It's difficult to convert
FormData
to an object, as thetoObject
doesn't handle multiple inputs with the samename
. These can be accessed withgetAll
, but the difficulty here is knowing which fields are arrays. I am currently handling this by giving all "multiple" inputs the ending "[]", but that is of course no option for a form library. Another option is, if there is an existing schema, parsing it somehow and handle it that way. I don't know how this might work, if it is posdible with both the built-in validation andzod
. Another thought: As I am usingzod
for validation, I foundzod-form-data
, which is exactly for validatingFormData
and has, as I find, a good approach, but not as much features aszod
itself.
So an additional question may be how to handle these differences. Add a layer to handle both formats? Or doing client side validation on FormData
too?
Do you have thoughts on this?
We are providing the form itself from the server, the formData object will be tested for validation and we already expect X amount of fields with the respective types.
We are providing the form itself from the server, the formData object will be tested for validation and we already expect X amount of fields with the respective types.
Can you elaborate this further? I am not sure if I understand it right. How would this solve the problem with different object shapes on server and client?
For example, if we create a login form, we will expect the given string to be an email, the checkbox as bool, the number as number/string (depending on the way implemented), etc.
I still don't get it 😅. This sounds basically just like validation. The problem I described was about how to use the same validation functionality for both server and client even if the given objects for these two different validations have not the same shape.
Another problem I encountered is that createServerAction$ behaves like createRouteAction and is executed client-side when defined outside the route in which it is used. This causes problems for us as a library, as it means that we cannot currently build on createServerAction$ in our code.
Do you know if there is a reason for that?
I think, the urls for the actions are generated from the route where they are called in. Maybe that's the cause?
If not, I think, we should open an issue.
~~And is there a workaround, like using a combination of createRouteAction
and server$
?~~
For what would we need this anyway? The forms are created in the routes, so it should work, I think?
@apollo79 thank you for your comment regarding the FormData object. I will continue to look around and also wait to see how SolidStart develops. Maybe a form library that is supposed to work in conjunction with SolidStart without JavaScript needs a completely different approach. It might also make more sense to provide an @modular-forms/solid-start
package that works a little differently under the hood.
Did you find anything interesting in the last days?
I could also think of an approach using a solid-start middleware
maybe, because server actions aren't really extendable as far as I see. But that would of course come with different problems like how to register actions. (I am just writing down my thoughts here so we can think of all possible options)