Open EuSouVoce opened 2 years ago
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:
FormData
to an object, as the toObject
doesn't handle multiple inputs with the same name
. These can be accessed with getAll
, 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 and zod
. Another thought: As I am using zod
for validation, I found zod-form-data
, which is exactly for validating FormData
and has, as I find, a good approach, but not as much features as zod
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
For what would we need this anyway? The forms are created in the routes, so it should work, I think? createRouteAction
and server$
?
@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)
Another interesting thing I saw, which would perhaps make it possible to get a similar behaviour to createServerAction$
are the possibilities with the basic server$
function. Its registerHandler
method might be really helpful.
Yes, I have a plan to make it work with createServerAction$
. It comes with a bit of boilerplate code, but for now it seems like the only possible option. From there we need to talk to the SolidStart team to improve the DX for both sides.
What exactly is your idea with server$
? As far as I know, the server$
functions are called via the Fetch API. This would mean that JavaScript is required in the browser for this.
What exactly is your idea with server$? As far as I know, the server$ functions are called via the Fetch API. This would mean that JavaScript is required in the browser for this.
It doesn't need JS. Submitting a form to the route where it is registered also works. Example:
import { type VoidComponent } from "solid-js";
import { isServer } from "solid-js/web";
import server$, { redirect } from "solid-start/server";
if (isServer) {
server$.registerHandler("/hello", (form: FormData, { request }: {request: Request}) => {
const response = redirect("/");
// set session header
// do stuff here...
return response;
});
}
const Home: VoidComponent = () => {
return (
<main>
<form action="/hello" method="post">
<input type="text" value="hello" />
<button type="submit">Submit</button>
</form>
</main>
);
};
export default Home;
This works even with JS completely disabled. I thought this could be helpful.
Thanks for the information! I will take that into consideration. I will probably start the implementation on Thursday or Friday. I will be happy to present my solution here so that we can improve the DX together.
Thanks for the information! I will take that into consideration. I will probably start the implementation on Thursday or Friday. I will be happy to present my solution here so that we can improve the DX together.
Nice! I look forward to it :-)
I just want to let you know that I probably won't be able to implement it until next week or the week after that. I'll give an update here then.
I just want to let you know that I probably won't be able to implement it until next week or the week after that. I'll give an update here then.
No problem! đ
I have more time again in the next few weeks and will actually get to work on the implementation soon.
I have been implementing Modular Forms for Qwik for the last two weeks and also experimenting with actions. The implementation for SolidJS is still pending, but I have already published the documentation of the API to be expected on our website. As soon as there is more news about it, I will report here. https://modularforms.dev/guides/handle-submission
Awesome! :rocket:
Hi, I'm getting this error on SolidStart when a page with form is the first one rendered: What might have caused it? Are there any extra steps needed when using this package with SolidStart SSR?
also this happens when trying to build app bundle:
Hi @awojciak, I see this problems for the first time. No, there are no special steps required. Have you installed Modular Forms via npm? Does everything work when you disable SSR? Can you share your code with me via GitHub or StackBlitz? Then I could try to help you.
@fabian-hiller 1) I've used yarn 2) yes, build works when ssr is disabled 3) no, I'm working in a private repo
Ok, yarn should not be the problem. I myself use pnpm for the Modular Forms website. For the website the build step with SSR works without any problems. So I suspect the problem is more with your setup, also because it's an import issues, on which I have no influence as a library. Without code or more info I can't help you though. If you want, you can create a new SolidStart project and try to reproduce the problem with minimal code. Then I could look over it and investigate the problem.
ok, I've found a solution - I had to add this to vite config:
ssr: { noExternal: ['@modular-forms/solid'] },
Thank you for taking the time to share your solution with us. If this happens again with another user, I will add it to the FAQ.
Hello everyone, I would like to give an update on this issue. With Modular Forms for Qwik I was able to fully integrate actions. This is due to the fact that with Qwik I could build on globalAction$
without any problems to provide a custom formAction$
API. With Solid Start this is currently not possible, but it is planned. So I prefer to wait until more information about this is published.
Not sure if this would be of interest, but the Conform library has done a good job of solving this problem for React and Remix.
Thank you for the tip!
It would be awsome if it runs and validates on the server side too...