Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

simple-stack-form

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

simple-stack-form

A simple form library for Astro projects

  • 0.1.5
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
815
increased by4.09%
Maintainers
1
Weekly downloads
 
Created
Source

Simple form

The simple way to handle forms in your Astro project 🧘‍♂️

---
import { z } from "zod";
import { createForm } from "simple:form";

const checkout = createForm({
  quantity: z.number(),
  email: z.string().email(),
  allowAlerts: z.boolean(),
});

const result = await Astro.locals.form.getData(checkout);

if (result?.data) {
  await myDb.insert(result.data);
  // proceed to checkout
}
---

<form method="POST">
  <label for="quantity">Quantity</label>
  <input id="quantity" {...checkout.inputProps.quantity} />

  <label for="email">Email</label>
  <input id="email" {...checkout.inputProps.email} />

  <label for="allowAlerts">Allow alerts</label>
  <input id="allowAlerts" {...checkout.inputProps.allowAlerts} />
</form>

Installation

Simple form is an Astro integration. You can install and configure this via the Astro CLI using astro add:

npm run astro add simple-stack-form

After installing, you'll need to add a type definition to your environment for editor hints. Add this reference to a new or existing src/env.d.ts file:

/// <reference types="simple-stack-form/types" />

Usage

Create a validated form

Type: createForm(ZodRawShape): { inputProps: Record<string, InputProps>, validator: ZodRawShape }

You can create a simple form with the createForm() function. This lets you specify a validation schema using Zod, where each input corresponds to an object key. Simple form supports string, number, or boolean (checkbox) fields.

import { createForm } from "simple:form";
import z from "zod";

const signupForm = createForm({
  name: z.string(),
  age: z.number().min(18).optional(),
  newsletterOptIn: z.boolean(),
});

createForm() returns both a validator and the inputProps object. inputProps converts each key of your validator to matching HTML props / attributes. The following props are generated today:

  • name - the object key.
  • type - checkbox for booleans, number for numbers, and text for strings.
  • aria-required - true by default, false when .optional() is used. Note aria-required is used to add semantic meaning for screenreaders, but leave room to add a custom error banner.

Our signupForm example generates the following inputProps object:

const signupForm = createForm({
  name: z.string(),
  age: z.number().min(18).optional(),
  newsletterOptIn: z.boolean(),
});

signupForm.inputProps;
/*
  name: { name: 'name', type: 'text', 'aria-required': true }
  age: { name: 'age', type: 'number', 'aria-required': false }
  newsletterOptIn: { name: 'newsletterOptIn', type: 'checkbox', 'aria-required': true }
*/

Handle array values

You may want to submit multiple form values under the same name. This is common for multi-select file inputs, or generated inputs like "add a second contact."

You can aggregate values under the same name using z.array() in your validator:

import { createForm } from "simple:form";
import z from "zod";

const contact = createForm({
  contactNames: z.array(z.string()),
});

Now, all inputs with the name contactNames will be aggregated. This uses FormData.getAll() behind the scenes:

---
import { createForm } from "simple:form";
import z from "zod";

const contact = createForm({
  contactNames: z.array(z.string()),
});

const res = await Astro.locals.form.getData(contact);
console.log(res?.data);
// contactNames: ["Ben", "George"]
---

<form method="POST">
  <label for="contact-1">Contact 1</label>
  <input id="contact-1" {...contact.inputProps.contactNames} />
  {res.fieldErrors?.contactNames?.[0]}
  <label for="contact-2">Contact 2</label>
  <input id="contact-2" {...contact.inputProps.contactNames} />
  {res.fieldErrors?.contactNames?.[1]}
</form>

Note that fieldErrors can be retrieved by index. For example, to get parse errors for the second input, use fieldErrors.contactNames[1].

Parse form requests

You can parse form requests from your Astro component frontmatter. Simple form exposes helpers to parse and validate these requests with the Astro.locals.form object.

getData()

Type: getData<T extends { validator: FormValidator }>(form: T): Promise<GetDataResult<T["validator"]> | undefined>

Astro.locals.form.getData() parses any incoming form request with the method POST. This will return undefined if no form request was sent, or return form data parsed by your Zod validator.

If successful, result.data will contain the parsed result. Otherwise, result.fieldErrors will contain validation error messages by field name:

---
import { z } from 'zod';
import { createForm } from 'simple:form';

const checkout = createForm({
  quantity: z.number(),
});

const result = await Astro.locals.form.getData(checkout);

if (result?.data) {
  console.log(result.data);
  // { quantity: number }
}
---

<form method="POST">
  <label for="quantity">Quantity</label>
  <input id="quantity" {...checkout.inputProps.quantity} />
  {
    result?.fieldErrors?.quantity?.map(error => (
      <p class="error">{error}</p>
    ))
  }
  ...
</form>
getDataByName()

Type: getDataByName<T extends { validator: FormValidator }>(name: string, form: T): Promise<GetDataResult<T["validator"]> | undefined>

You may have multiple forms on the page you want to parse separately. You can define a unique form name in this case, and pass the name as a hidden input within the form using <FormName>:

---
import { z } from 'zod';
import { createForm } from 'simple:form';

const checkout = createForm({
  quantity: z.number(),
});

const result = await Astro.locals.form.getDataByName(
  'checkout',
  checkout,
);

if (result?.data) {
  console.log(result.data);
  // { quantity: number }
}
---

<form method="POST">
  <label for="quantity">Quantity</label>
  <input id="quantity" {...checkout.inputProps.quantity} />

  <FormName value="checkout" />
  <!--Renders the following hidden input-->
  <!--<input type="hidden" name="_formName" value="checkout" />-->
</form>

Client validation

Astro supports any UI component framework. To take advantage of this, simple form helps generate a client-validated form in your framework of choice.

⚠️ Client validation relies on Astro view transitions. Ensure view transitions are enabled on your page.

Create a form with the simple-form CLI

You can generate a client form component with the simple-form create command:

# npm
npx simple-form create

# pnpm
pnpm dlx simple-form create

This will output a form component in your directory of choice.

🙋‍♀️ Why code generation?

We know form libraries have come and gone over the years. We think the reason is ahem simple: forms are just hard. There's countless pieces to tweak, from debounced inputs to live vs. delayed validation to styling your components.

So, we decided to take a hint from the popular shadcn/ui library and pass the code off to you.

We expose internal functions to manage your form state and handle both synchronous and asynchronous validation. Then, we generate components with accessible defaults based on the "Reward now, punish late" pattern. We invite you to tweak and override the code from here!

Usage

An demo can be found in our repository examples:

Sanitizing User Input

You may need to sanitize user input with rich text content. This is important for any text rendered as HTML to prevent Cross-Site Scripting (XSS) attacks. You can use the sanitize-html library for this:

npm install --save sanitize-html
npm install --save-dev @types/sanitize-html

Next, call sanitize-html from your text validator with a Zod transform():

+ import sanitizeHtml from "sanitize-html";

const signupForm = createForm({
-  name: z.string(),
+  name: z.string().transform((dirty) => sanitizeHtml(dirty)),
  age: z.number().min(18).optional(),
  newsletterOptIn: z.boolean(),
});

Examples

You can find a sanitization implementation example on our examples

Keywords

FAQs

Package last updated on 26 Dec 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc