Simple React Forms with Canathus




View on Medium

Writing forms in React can be a strenuous process. For just one piece of information, we often have multiple pieces of state containing error messages and validation. Not only does this get confusing pretty quickly, but it also creates bulky react files where no-one knows where to find anything. It also doesn’t feel very elegant, and that has to count for something…

With canathus, each input data handles its own error state and messages. This allows you to focus on the important functionality, and visually simplifies react files. In this article, we’ll build a simple react form to collect basic personal information with canathus.

(Please note: As I did build this library, some may argue that biases may be present)

Setting up the form

We’ll start by setting up the basic semantics of our form. Copy the following jsx into your form component.

function Form() {
  return (
    <div className="container">
      <h1>Personal Details</h1>
      <form>
        <label htmlFor="nameInput">Name</label>
        <input id="nameInput"></input>

        <label htmlFor="emailInput">Email</label>
        <input id="emailInput" type="email"></input>

        <label htmlFor="ageInput">Age</label>
        <input id="ageInput" type="number"></input>

        <button>Submit</button>
      </form>
    </div>
  );
}

Validation (please)

Photo by Rowen Smith on Unsplash

With our basic setup in hand, we can move towards the validation of our form. Canathus uses validators, functions which are passed to the input setup which are then run whenever you attempt to validate that piece of information.

For this setup, we’re going to have our validators set up within a validator folder. We can then import these and use them to check the values submitted through our form.

Add the following files into the src/validators folder:

export const nameValidator = (value) => {
  if (value.length <= 0)
    return { valid: false, errorMsg: "This field is required" };

  return {
    valid: true,
    errorMsg: "",
  };
};
export const emailValidator = (value) => {
  if (value.length <= 0)
    return { valid: false, errorMsg: "This field is required" };

  // Check for email pattern
  if (
    !value.match(
      /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    )
  )
    return { valid: false, errorMsg: "Please enter an email address" };

  return {
    valid: true,
    errorMsg: "",
  };
};
export const ageValidator = (value) => {
  if (value.length <= 0)
    return { valid: false, errorMsg: "This field is required" };

  if (value < 0) return { valid: false, errorMsg: "Age must be positive" };

  if (value >= 150) return { valid: false, errorMsg: "Age must be below 150" };

  return {
    valid: true,
    errorMsg: "",
  };
};

Feel free to customise these validators to your liking, but these should suffice for now.

Getting into (a) state

Photo by Weiye Tan on Unsplash

We can now start setting up the state for our form. Firstly, we actually need canathus in our project:

npm install canathus

With canathus installed, we can use the provided useInput hook to instantiate our form data. This hook takes two inputs, the initial value and the validator function.

import { useInput } from "canathus";

function Form() {
  const [name, setName] = useInput("", nameValidator);
  const [email, setEmail] = useInput("", emailValidator);
  const [age, setAge] = useInput("", ageValidator);
...

Let’s now link this state to our jsx form. We’re going to use values provided by canathus to set up error handling.

import { useInput } from "canathus";
import { nameValidator } from "./validators/nameValidator.js";
import { emailValidator } from "./validators/emailValidator.js";
import { ageValidator } from "./validators/ageValidator.js";

function App() {
  const [name, setName] = useInput("", nameValidator);
  const [email, setEmail] = useInput("", emailValidator);
  const [age, setAge] = useInput("", ageValidator);

  return (
    <div className="container">
      <h1>Personal Details</h1>
      <form>
        <label htmlFor="nameInput">Name</label>
        <input
          id="nameInput"
          value={name.value}
          onChange={(e) => setName(e.target.value)}
        ></input>
        <span>{name.error && name.errorMsg}</span>

        <label htmlFor="emailInput">Email</label>
        <input
          id="emailInput"
          type="email"
          value={email.value}
          onChange={(e) => setEmail(e.target.value)}
        ></input>
        <span>{email.error && email.errorMsg}</span>

        <label htmlFor="ageInput">Age</label>
        <input
          id="ageInput"
          type="number"
          value={age.value}
          onChange={(e) => setAge(e.target.value)}
        ></input>
        <span>{age.error && age.errorMsg}</span>

        <button>Submit</button>
      </form>
    </div>
  );
}

export default App;

Submission… or else

We can now create a dummy submit method, which will most likely be replaced with an API call in a full application. We can use the validate method provided by canathus to validate all of our input data and automatically update their error messages.

import { useInput, validate } from "canathus";

... 

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("Valid:", validate({ name, email, age }));
  };

...

With this submission function added, our complete code for our form component should look like this:

import { useInput, validate } from "canathus";
import { nameValidator } from "./validators/nameValidator.js";
import { emailValidator } from "./validators/emailValidator.js";
import { ageValidator } from "./validators/ageValidator.js";

export function Form() {
  const [name, setName] = useInput("", nameValidator);
  const [email, setEmail] = useInput("", emailValidator);
  const [age, setAge] = useInput("", ageValidator);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("Valid:", validate({ name, email, age }));
  };

  return (
    <div className="container">
      <h1>Personal Details</h1>
      <form onSubmit={(e) => handleSubmit(e)}>
        <label htmlFor="nameInput">Name</label>
        <input
          id="nameInput"
          value={name.value}
          onChange={(e) => setName(e.target.value)}
        ></input>
        <span>{name.error && name.errorMsg}</span>

        <label htmlFor="emailInput">Email</label>
        <input
          id="emailInput"
          type="email"
          value={email.value}
          onChange={(e) => setEmail(e.target.value)}
        ></input>
        <span>{email.error && email.errorMsg}</span>

        <label htmlFor="ageInput">Age</label>
        <input
          id="ageInput"
          type="number"
          value={age.value}
          onChange={(e) => setAge(e.target.value)}
        ></input>
        <span>{age.error && age.errorMsg}</span>

        <button>Submit</button>
      </form>
    </div>
  );
}

Pièce de Résistance

With that we have a functional, validated form in React. Try inputting some invalid data and check that the error messages are showing up as intended. Admittedly some styling wouldn’t hurt, our current masterpiece is a bit of an eyesore. If you want to see an example of a more modern form I made earlier, check out the demo below (from the canathus website).

Hopefully you enjoyed creating a form with canathus, and will consider it for your future projects. Apologies for leaving on such a dramatic cliffhanger, but some exciting features are coming soon…

https://oflint-1.github.io/canathus/