1.4.2 • Published 10 months ago

frrm v1.4.2

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

🐇 frrm

npm.io npm.io

Tiny 0.5kb Zod-based, HTML form abstraction that goes brr.

⭐ If you find this tool useful please consider giving it a star on Github ⭐

Basic Example

JavaScript

import { create, attach } from 'frrm'
import { z } from 'zod'

const handler = create({
  /**
   * If string value then will replace submit button text with provided value
   * while server request is resolving. Will also disable all buttons and
   * inputs. If `true` is passed the label wont be replaced, but everything will
   * still disable. Alternatively you can pass a callback if you want to
   * manually handle the busy state (will prevent default behaviour).
   */
  onBusy: "Loading...",

  /**
   * Applies client-side Zod validation to determine whether `onSubmit` should
   * fire.
   */
  schema: z.object({
    email: z.string().min(1, { message: "Email value is required" }).email({
      message: "Email is not formatted correctly",
    }),
    password: z
      .string()
      .min(1, {
        message: "Password value is required",
      })
      .min(6, {
        message: "Password is required to be at least 6 characters",
      }),
  }),

  /**
   * Will inject error message into the provided DOM element. Alternatively a
   * callback can be provided that accepts both `timestamp` and `value`
   * properties. Note that error is automatically removed when the form is
   * submitted again - likewise a `null` value will be passed to the callback
   * (if used).
   */
  onError: document.querySelector('[role="alert"]')!,

  onSubmit: (submission) => {
    /**
     * Fake server request that takes 4 seconds to resolve, and throws on
     * incorrect email or password.
     */
    return new Promise((resolve) => {
      try {
        setTimeout(() => {
          if (submission.email !== "john@example.com")
            resolve(Error("Invalid email"));

          if (submission.password !== "hunter2")
            resolve(Error("Invalid password"));

          resolve(undefined);
        }, 4000);
      } catch (error) {
        console.error(error);
        resolve(Error("Something went wrong"));
      }
    });
  },
});

/**
 * If you are using a framework like React, then you can simply pass the
 * instance to the onSubmit handler. However, if you are using plain JavaScript,
 * then you need to attach the event listener manually. You can use the `attach`
 * function to do this if you want, since it provides a returned object with a
 * `remove` method for cleanup.
 */
attach(document.querySelector("form")!, handler);

CSS

@keyframes enter {
  from {
    transform: translateY(-0.2rem);
    opacity: 0.5;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

[role="alert"] > * {
  background: rgba(255, 0, 0, 0.05);
  padding: 1rem;
  animation: enter 0.3s ease;
}

HTML

<form>
  <label>
    <span>Email:</span>
    <input type="email" name="email" />
  </label>

  <div>
    <label>
      <span>Password:</span>
      <input type="password" name="password" />
    </label>
  </div>

  <div role="alert" aria-live="assertive"></div>
  <button type="submit">Login</button>
</form>

React Example

When using React you can simply pass the handler as is to onSubmit.

import { z } from "zod";
import { useState } from "react";
import { create } from "./react";

export const Example = () => {
  const [message, setMessage] = useState({
    value: null,
    timestamp: Date.now(),
  });

  return (
    <form
      className="form"
      onSubmit={create({
        schema,
        onSubmit: fromServer,
        onError: setMessage,
        onBusy: "Loading...",

        schema: z.object({
          email: z.string().min(1, { message: "Email value is required" }).email({
            message: "Email is not formatted correctly",
          }),
          password: z
            .string()
            .min(1, {
              message: "Password value is required",
            })
            .min(6, {
              message: "Password is required to be at least 6 characters",
            }),
        }),
      })}
    >
      <label>
        <span>Email:</span>
        <input type="email" name="email" />
      </label>

      <div>
        <label>
          <span>Password:</span>
          <input type="password" name="password" />
        </label>
      </div>

      <div>
        {message.value && (
          <div className="message" key={`${message.value}-${message.timestamp}`}>
            {message.value}
          </div>
        )}
      </div>

      <button type="submit">
        Login
      </button>
    </form>
  );
};

Is it really 0.5kb?

Pretty much. Technically it is ~0.537kb . This is the minified code:

const e=e=>{const{onSubmit:t,schema:r,onError:a}=e;return async e=>{e.preventDefault(),a({value:null,timestamp:Date.now()});const n=e.currentTarget,o=Object.fromEntries(new FormData(n));try{const e=r.parse(o),n=await t(e);n&&a({value:n,timestamp:Date.now()})}catch(e){if(e.errors.length)return n.querySelector(`[name="${e.errors[0].path[0]}"]`).focus(),a({value:e.errors[0].message,timestamp:Date.now()});throw e}}},t=(e,t)=>(e.addEventListener("submit",t),{remove:()=>e.removeEventListener("submit",t)});export{t as attach,e as create};
1.4.2

10 months ago

1.3.9

10 months ago

1.3.7

10 months ago

1.3.6

10 months ago

1.3.4

10 months ago

1.3.2

10 months ago

1.3.1

10 months ago

1.0.9

10 months ago

1.0.8

10 months ago

1.0.6

10 months ago

1.0.5

10 months ago

1.0.4

10 months ago

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

10 months ago