Skip to content

Commit

Permalink
feat(form): add solid-js example
Browse files Browse the repository at this point in the history
  • Loading branch information
dsnjunior committed Jan 3, 2024
1 parent 6d8ebbf commit 32411bb
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 12 deletions.
2 changes: 2 additions & 0 deletions examples/playground/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import simpleStackStream from "simple-stack-stream";
import react from "@astrojs/react";
import node from "@astrojs/node";
import preact from "@astrojs/preact";
import solidJs from "@astrojs/solid-js";
import tailwind from "@astrojs/tailwind";

// https://astro.build/config
Expand All @@ -14,6 +15,7 @@ export default defineConfig({
simpleStackStream(),
react({ include: ["**/react/*"] }),
preact({ include: ["**/preact/*"] }),
solidJs({ include: ["**/solid-js/*"] }),
tailwind(),
],
adapter: node({
Expand Down
2 changes: 2 additions & 0 deletions examples/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@astrojs/node": "^7.0.0",
"@astrojs/preact": "^3.0.1",
"@astrojs/react": "^3.0.7",
"@astrojs/solid-js": "^3.0.3",
"@astrojs/tailwind": "^5.0.3",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
Expand All @@ -25,6 +26,7 @@
"sanitize-html": "^2.11.0",
"simple-stack-form": "^0.1.0",
"simple-stack-stream": "^0.0.3",
"solid-js": "^1.8.7",
"tailwindcss": "^3.0.24",
"zod": "^3.22.4"
},
Expand Down
139 changes: 139 additions & 0 deletions examples/playground/src/components/solid-js/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/** @jsxImportSource solid-js */

// Generated by simple:form

import {
createSignal,
createContext,
useContext,
type ComponentProps,
Show,
For,
} from "solid-js";
import {
type FieldErrors,
type FormState,
type FormValidator,
getInitialFormState,
toSetValidationErrors,
toTrackAstroSubmitStatus,
toValidateField,
validateForm,
formNameInputProps,
} from "simple:form";

export function useCreateFormContext(
validator: FormValidator,
fieldErrors?: FieldErrors,
) {
const initial = getInitialFormState({ validator, fieldErrors });
const [formState, setFormState] = createSignal<FormState>(initial);
return {
value: formState,
set: setFormState,
setValidationErrors: toSetValidationErrors(setFormState),
validateField: toValidateField(setFormState),
trackAstroSubmitStatus: toTrackAstroSubmitStatus(setFormState),
};
}

export function useFormContext() {
const formContext = useContext(FormContext);
if (!formContext) {
throw new Error(
"Form context not found. `useFormContext()` should only be called from children of a <Form> component.",
);
}
return formContext;
}

type FormContextType = ReturnType<typeof useCreateFormContext>;

const FormContext = createContext<FormContextType | undefined>(undefined);

export function Form(
props: {
validator: FormValidator;
context?: FormContextType;
fieldErrors?: FieldErrors;
} & Omit<ComponentProps<"form">, "method" | "onSubmit">,
) {
const formContext =
props.context ?? useCreateFormContext(props.validator, props.fieldErrors);

return (
<FormContext.Provider value={formContext}>
<form
{...props}
method="post"
onSubmit={async (e) => {
const formData = new FormData(e.currentTarget);
formContext.set((formState) => ({
...formState,
isSubmitPending: true,
submitStatus: "validating",
}));
const parsed = await validateForm({
formData,
validator: props.validator,
});
if (parsed.data) {
return formContext.trackAstroSubmitStatus();
}

e.preventDefault();
e.stopPropagation();
formContext.setValidationErrors(parsed.fieldErrors);
}}
>
<Show when={props.name}>
{(name) => <input {...formNameInputProps} value={name()} />}
</Show>
{props.children}
</form>
</FormContext.Provider>
);
}

export function Input(inputProps: ComponentProps<"input"> & { name: string }) {
const formContext = useFormContext();
const fieldState = () => formContext.value().fields[inputProps.name];
if (!fieldState()) {
throw new Error(
`Input "${inputProps.name}" not found in form. Did you use the <Form> component?`,
);
}

return (
<Show when={fieldState()}>
{(state) => (
<>
<input
onBlur={(e) => {
const value = e.target.value;
if (value === "") return;
formContext.validateField(
inputProps.name,
value,
state().validator,
);
}}
onInput={(e) => {
if (!state().hasErroredOnce) return;
const value = e.target.value;
formContext.validateField(
inputProps.name,
value,
state().validator,
);
}}
{...inputProps}
/>
<For each={state().validationErrors}>
{(e) => <p class="text-red-400">{e}</p>}
</For>
</>
)}
</Show>
);
}
60 changes: 60 additions & 0 deletions examples/playground/src/components/solid-js/Signup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/** @jsxImportSource solid-js */
import { type JSX, Show } from "solid-js";
import { z } from "zod";
import { Form, Input, useFormContext } from "./Form";
import { type FieldErrors, createForm } from "simple:form";

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export const signup = createForm({
username: z
.string()
.min(2)
.refine(async (s) => {
await sleep(400);
return s !== "admin";
}),
optIn: z.boolean().optional(),
});

export default function Signup(props: {
serverErrors?: FieldErrors<typeof signup>;
}) {
return (
<Form
class="flex flex-col gap-2 items-start"
fieldErrors={props.serverErrors}
validator={signup.validator}
name="signupSolid"
>
<FormGroup>
<label for="name">Name</label>
<Input id="name" {...signup.inputProps.username} />
</FormGroup>
<FormGroup>
<label for="optIn">Opt in</label>
<Input id="optIn" {...signup.inputProps.optIn} />
</FormGroup>
<button
type="submit"
class="bg-purple-700 rounded px-5 py-2 disabled:bg-purple-900"
>
Submit
</button>
<Loading />
</Form>
);
}

function FormGroup(props: { children: JSX.Element }) {
return <div class="flex gap-3 items-center">{props.children}</div>;
}

function Loading() {
const formContext = useFormContext();
return (
<Show when={formContext.value().isSubmitPending}>
<p>{formContext.value().submitStatus}</p>
</Show>
);
}
52 changes: 40 additions & 12 deletions examples/playground/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,29 @@ import SignupReact, { signup as signupReact } from "../components/react/Signup";
import SignupPreact, {
signup as signupPreact,
} from "../components/preact/Signup";
import SignupSolid, {
signup as signupSolid,
} from "../components/solid-js/Signup";
import Sanitize, { sanitize } from "../components/Sanitize";
import { ViewTransitions } from "astro:transitions";
const { form } = Astro.locals;
const formResultReact = await form.getDataByName("signupReact", signupReact);
const formResultPreact = await form.getDataByName("signupPreact", signupPreact);
if (formResultReact?.data) {
console.log(formResultReact.data);
}
if (formResultPreact?.data) {
console.log(formResultPreact.data);
}
const formResultSolid = await form.getDataByName("signupSolid", signupSolid);
const sanitizeFormResult = await form.getDataByName("sanitize", sanitize);
if (sanitizeFormResult?.data) {
console.log(sanitizeFormResult.data);
}
[
formResultReact,
formResultPreact,
formResultSolid,
sanitizeFormResult
].forEach((formResult) => {
if (formResult?.data) {
console.log(formResult.data);
}
});
await new Promise((resolve) => setTimeout(resolve, 400));
---
Expand Down Expand Up @@ -79,7 +82,32 @@ await new Promise((resolve) => setTimeout(resolve, 400));
client:load
/>
</div>

<hr />

<h2>Solid</h2>
{
formResultSolid?.data && (
<div
class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative"
role="alert"
>
<strong class="font-bold">Success!</strong>
<span class="block sm:inline">
You have successfully submitted the form.
</span>
</div>
)
}
<div transition:name="solid-form">
<SignupSolid
serverErrors={formResultSolid?.fieldErrors}
client:load
/>
</div>

<hr />

<h2>Sanitize</h2>
<div class="pb-2">
Try pasting this code snippet into each field once and submit
Expand Down

0 comments on commit 32411bb

Please sign in to comment.