DEV Community

Cover image for TanStack Form Tutorial: Setup and simple validation (with shadcn/ui)
Leonardo Montini for This is Learning

Posted on • Edited on • Originally published at leonardomontini.dev

TanStack Form Tutorial: Setup and simple validation (with shadcn/ui)

Forms are not an easy business 🤯

Luckily there are many libraries helping out with that and today let's see up close how to setup a form with TanStack Form, the form library in the TanStack ecosystem.

It works on React, but also Angular, Vue, Solid, and Lit.

It’s Headless so you can use it with whatever UI components you have in your project and as with everything in TanStack it’s type-safe! In this example we'll use components from shadcn/ui to build the form but you can really make it work with any UI library.

Worth mentioning it’s probably not production-ready yet, but if you’re curious it definitely deserves a try (maybe on side projects) and feedback can indeed help get to v1, which should happen soon 👀

About This tutorial

Demo Form

My idea is to cover this library in 3 steps. The first one (this article) is about setting up the library and adding some simple validation rules. The second one will be about more complex validation rules and the third one about handling arrays and dynamic fields.

You can already find the video version for chapter 1 here:

Setup

As usual, everything begins with an installation:

npm i @tanstack/react-form
Enter fullscreen mode Exit fullscreen mode

Notice here that we are installing @tanstack/react-form, if you're using Angular, Vue, Solid, or Lit you should install the respective package.

And... that's it! No particular setup is needed, you can start using it right away.

Form

Let's imagine we're starting from this uncontrolled form in our component:

<form className="flex flex-col gap-4">
  <div>
    <Label htmlFor="username">Username</Label>
    <Input id="username" type="text" />
  </div>
  <div>
    <Label htmlFor="passowrd">Passowrd</Label>
    <Input id="password" type="password" />
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

To add the magic, we can call the useForm hook and get our form instance.

const form = useForm({
  defaultValues: {
    username: '',
    password: '',
  },
  onSubmit: (values) => {
    console.log(values);
  },
});
Enter fullscreen mode Exit fullscreen mode

To avoid unexpected side effects, let's prevent the default form submission:

<form
  className="flex flex-col gap-4"
  onSubmit={(e) => {
    e.preventDefault();
    e.stopPropagation();
  }}
>
  ...
</form>
Enter fullscreen mode Exit fullscreen mode

Input

It's now time to integrate the inputs with the form, through the Field api exposed by TanStack Form.

<form.Field
  name="username"
  children={(field) => (
    <>
      <Label htmlFor="username">Username</Label>
      <Input id="username" type="text" value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />
    </>
  )}
/>
Enter fullscreen mode Exit fullscreen mode

Same goes for the password field, we're basically passing our inputs as children of the Field component and we're using the field object to get the value and the handleChange function.

Submit & Reset

To submit or reset the form, the api exposes form.handleSubmit and form.reset.

The easiest way to submit a form is to add a button with type="submit" and call form.handleSubmit on click.

<button type="submit" onClick={form.handleSubmit}>
  Submit
</button>
Enter fullscreen mode Exit fullscreen mode

As an alternative, you can indeed add form.handleSubmit to the onSubmit event of the form.

And to reset the form, we can call form.reset on click.

<button type="reset" onClick={form.reset}>
  Reset
</button>
Enter fullscreen mode Exit fullscreen mode

note: in the video it's slightly different because the buttons are outside the form in a CardFooter component, but the idea remains the same:

<CardFooter className="flex justify-between">
  <Button variant="outline" onClick={form.reset}>
    Reset
  </Button>
  <Button onClick={form.handleSubmit}>Sign Up</Button>
</CardFooter>
Enter fullscreen mode Exit fullscreen mode

Validation

The Field component also accepts a validators object where you can define a function returning the errors, that can be run at the desired event.

Currently supported are: onBlur, onChange, onMount and onSubmit.

Our username can be validated as follows:

validators={{
    onChange: ({ value }) => value.length < 3
        ? "Username must be at least 3 characters long"
        : undefined,
}}
Enter fullscreen mode Exit fullscreen mode

With that, you'll notice that if you try to submit the empty form... nothing happens! That's because we're not showing the errors yet.

In general, errors are inside field.state.meta.errors. You can also know where they come from (which event in the validation) but let's keep it simple for now. Let's add this right below the input:

{
  field.state.meta.errors && <p className="text-red-500 text-sm mt-1">{field.state.meta.errors}</p>;
}
Enter fullscreen mode Exit fullscreen mode

The password validator can be a bit longer, to show better error messages:

onChange: ({ value }) => {
    if (value.length < 6) {
        return "Password must be at least 6 characters long";
    }

    if (!/[A-Z]/.test(value)) {
        return "Password must contain at least one uppercase letter";
    }

    if (!/[a-z]/.test(value)) {
        return "Password must contain at least one lowercase letter";
    }

    if (!/[0-9]/.test(value)) {
        return "Password must contain at least one number";
    }
},
Enter fullscreen mode Exit fullscreen mode

Async validation

At this point, each time a value is changed, the validation function is called and the errors are shown. But what if we want to validate the username against an API or if we simply want to avoid running it that often?

As easy as turning onChange to onChangeAsync and adding an extra property called onChangeAsyncDebounceMs.

This already works!

Validation libraries

TanStack Form also supports integration with popular validation libraries like Zod, Yup and Valibot through adapters. We'll see that on chapter 3 (or you can check the docs).

Conclusion

This was a quick overview of how to setup a form with TanStack Form and add some simple validation rules. Stay tuned for the next chapters where we'll see more complex validation rules and how to handle arrays and dynamic fields... and even more topics if you like it too!

As mentioned in the intro, the form isn't production-ready yet but being in the TanStack ecosystem is a good sign that something interesting might come out of it.

If you want to support the development of the project you can contribute to the GitHub Repo as I'm doing. You'll also find some previews such as this PR which will extend the validation feature.

The source code of the demo created with this article is also available on GitHub (Balastrong/tanstack-form-demo) and will get updated with the next chapters.

Last call, if you want to see it in action, you can watch the video version of this article on my YouTube channel: TanStack Form: Setup and simple validation (with shadcn/ui).


Thanks for reading this article, I hope you found it interesting!

I recently launched a GitHub Community! We create Open Source projects with the goal of learning Web Development together!

Join us: https://github.com/DevLeonardoCommunity

Do you like my content? You might consider subscribing to my YouTube channel! It means a lot to me ❤️
You can find it here:
YouTube

Feel free to follow me to get notified when new articles are out ;)

Top comments (6)

Collapse
 
chrischism8063 profile image
chrischism8063 • Edited

Great write up.

So many ways to build forms, but I love using useQuery and useMutations.

I've been pulling my projects away from Redux (someone else designed these projects but I've grown to hate it more and more so I wanted to switch it out).

I'm searching a better way to temporarily save for data in case the post throws errors.

Good luck on your journey.

Collapse
 
ajinkyax profile image
Ajinkya Borade
Collapse
 
balastrong profile image
Leonardo Montini

Sure! I'll mention it in the next chapter where I talk about advanced validation :)

Collapse
 
machineno15 profile image
Tanvir Shaikh

every new library makes form handling more complicated then before.

Collapse
 
balastrong profile image
Leonardo Montini

Form handling is a complicated craft by itself :D
This library is not on a stable version yet and as far as I know they're happy to receive suggestions and proposal, if you have some ideas on better APIs I'd say it's a great time to suggest them!

Collapse
 
claudye profile image
Claude Fassinou

Good Job!

Can you try this also: github.com/trivule/trivule ??