Building out forms ranks as one of the highest priorities in web development. It facilitates user interaction for the broader application. It is your gateway to providing the rest of your application clean data.
It can also be very tedious.
Over the weekend, I began playing with the idea about how I could build forms without needing to write any JSX. I wanted to take a format (like JSON) that could be produced by anyone and use that to generate out the form with validation.
What are the benefits of doing this?
- Re-useability: The ability to take from one project to another and reproduce the form output.
- Compose-ability: It enables configurations to be combined with ease ie multiple config files being merged for more complex forms.
- Upgradeability: It centralizes the point where I can make adjustments in the future.
- Flexibility: It is easy to provide JSON format. The endgame is to be very meta and build a form that itself builds forms.
As a fair warning, this project uses my personal (but currently private) design system to create the files.
While the output itself will be custom components, the approach to doing so is translatable to anything thing you want. This includes using the general HTML tags of
This tutorial is a lot more rough-around-the-edges and scrappy than my others. There is much I could do to clean this up, but I hope that this will show you how I begin projects by being scrappy and validating my goals. I have made adjustments to the code since.
We are going to be using Deno and Snowpack. I wanted an excuse to try both out as I’ve heard so many good things! Yes, this is my first time playing around with both. I wasn’t so quick on adopting these technologies.
Spoiler alert: both great.
Check the installation guide for Deno to set up for this project as it required, but Mac users can use the trusty Brew install.
Setting up a TypeScript React project with Snowpack
We will set up a basic project for this to run in, but we won’t be using it for much other than running our app and seeing the results.
We will use Create Snowpack App to get up and going with a React template.
Once installed, if we run
yarn start then it will boot up a dev server with the familiar React starter page!
Perfect. Let’s move onto generating the template.
The game plan
So the plan is this:
- Learn how to read and write files with Deno.
- Write a reuseable template string that I can test out (for building the forms).
- Have this template string build out using my personal Design System components.
- Have it also build out form validation using Yup.
- Come up with a re-useable JSON schema (which can be subject to change).
Let’s get started with a short “Hello, World!” CLI with Deno.
At this point, it required a bunch of running through Deno’s documentation to find the packages that I needed.
For the sake of brevity, I will leave all the different Deno documentation resources for the end.
I knew what I wanted to be able to do:
- Parse arguments given from the command line to provide the config path.
- Read that config and use it.
From prior experience, I had an inkling on what to Google for (file readers, command line parsing, etc in Deno) so I went to work and found the
parse module and built-in
readFileSync from the
std library were what I needed.
Let’s make a script file and see it in action:
Now we can add six simple lines of code to
In this code, we are telling Deno to:
- Parse the arguments given when we run the program.
- Use a
TextDecoderto read the file path that we will give as the first argument (indexed at 0).
- Read that file synchronously.
- Decode the file contents and assign it to JSON.
- Log the JSON.
Now we can run the following:
--allow-read flag gives Deno read permissions during runtime to allow us to read
argv, the values of
_ begin after running the program with
deno run --allow-read bin/generate-form.ts, so our argument
data/basic-form.json becomes the first index of
_, hence the
argv._ being parse to the
Let’s now move onto the more complex work.
Designing the form schema
A lot of this came from what props I provide my components, but I opted to update
data/generate-form.json with something like this:
Here, I decided that I would have a top-level name property for the form and then elements as an array of what I want in the form. I decided to go this way to enable the top-level room for more metadata as I may need it down the track.
As for the structure of each element, I decided that they need an
required, but anything else there can be optionally used to help create the validations and other metadata.
The types here relate directly to a component I have in my design system and map the following:
- text —
- textarea —
- date —
- checkbox —
- select —
- radio —
Between these six, I have most of what I need for my forms.
Building out the simple form
The following relates to how I ended up writing out the form. As there can be a lot of code, I will paste them in order of what is in the final file and explain a little on each.
If you would like, follow along as I add the code bit-by-bit, but I will leave the final code at the end too.
I want to use the
name field to help generate components, so I added in
camelCase from Deno's
case module to help alter the casing when required.
I also adding in the
prettier modules to help with formatting the files afterward. We are going to use string interpolation, so it will never be that pretty.
Parsing the file
Nothing new here from above, so I’ll move on.
Setting up types
This expects you to understand TypeScript. In general, I create the FieldType to match my “type” decisions explained early
I decided on an initial Config option that would house my different arrays of strings required for string interpolation.
The FormElement matches directly to how I structure my
elements for the JSON files (and include their optional values).
FormType is an enum so I could use things like
FormType.text instead of always writing text and being susceptible to misuse.
As mentioned, I wanted to start the form with string interpolation. A lot of this was identifying the points where I could repeat code (ie the elements, the component name, etc) and abstracted them so I could generate the strings and interpolate them all into one.
It’s worth noting that you don’t need to copy
.replace(/\s/g, ""everywhere like I did. Again, it was simply to get things up and going. All this code is before refinement.
You can read through, but the gist of it is string manipulation based on the JSON files. The types I have above for them
FormElement should help understand the properties available to each argument.
These generators create a custom name for my component, add in the components within the JSX, add validation, and are prepped to set up things that work outside of React Hook Form.
I’m not going to delve too deep into React Hook Form, but it was the library that I opted to use. Why? I just went looking through the different options and felt it was the simplest to implement and had performance perfs (controlled input sometimes causes me headaches).
Running the build config
Finally, the last part is essentially calling a “main” function
buildConfig that generates a config on the fly by using the helper generator functions, then will finish up by using Prettier to format the code and then write the code out to a path that I desired.
That path “./src/NewForm.tsx” was changed later to become an
So it looks like a lot is happening since there is a lot of code, but really it is just one big interpolated string.
The form in action
We can now run our final code using the following:
--allow-writeto our previous run for permissions.
If successful, we will have a new file
Let’s update our
App.tsx file. Mine looked like so:
If we hit save, our local environment will reload (incredibly fast thanks to Snowpack) and show us our form!
The basic form output from the generator
The form in action
The form after submitting
If you stopped the Snowpack server before, just run
yarn startagain from the CLI.
With the auto-generated form, validations, and the onSubmit prop to show use the data, we can now play around with it and see that our code is working as expected.
This was just a quick recount of how I got things up and running yesterday.
I made some changes later to introduce more complex JSON files that started adding in my other types.
More complex example
Unlike my other tutorials, this is a rough-around-the-edges example of getting things up and going and how you can too! I’ll leave you with the final code and resources so I can begin my work week!
Resources and Further Reading
- Deno Parse Args
- Deno Read File Sync
- Deno — Case
- Deno — Prettier
- Prettier — Parsers
- Snowpack CSA
- React Hook Form API
- React Hook Form — Examples
- Build your own JS generator
- Hygen template generator
- Yup validation
Image credit: Mikita Yo
Originally posted on my blog.