Editor’s note: This article was last updated on 19 July, 2022 to include more detailed code descriptions and information on troubleshooting dynamic forms in Vue.js.
Forms are the bread and better of getting user input in web applications, but sometimes we just don’t know how many inputs we need. For example, users may need to enter previous jobs, or register several items, but the exact number is unknown, and we need to populate the page with the right number of inputs. This is where dynamic forms come to save the day. With some clever logic and using some of Vue’s powerful directives we can let the user create as many sets of inputs as they need to detail each job or item.
Vue enables developers to create forms in their applications just as they would in plain HTML5. This means you need little to no additional skills to create forms. Vue supports all the elements that HTML5 supports, such as input
, text area
, label
, and more.
In this tutorial, you’ll learn how to make Vue forms dynamic. Dynamic forms enable you to give your users enough control to dictate outcomes.
First, install Node.js v10 or above (you can verify whether you already have it by running the following command):
node -v
Next, install the latest version of Vue globally on your machine, then install Vue CLI v3.0 (you must first uninstall the old CLI version with the following command):
npm uninstall -g vue-cli
Then, install the new one:
npm install -g @vue/cli
Run npx degit
viclotana/vue-canvas
vue-canvas
to create a copy of the project, and navigate into the vue-canvas
folder to run the following command and install dependencies:
npm install
When building a Vue form, it can be difficult to determine the number of outcomes associated with a given field. A good way to address this is to personalize the user experience by giving the user control.
Take LinkedIn, for example: when you fill out your employment details, there is a field labeled “Current Position” and a link to add a new position. Here you are supposed to enter your current role as well as any previous jobs you’d like to list.
Obviously, LinkedIn doesn’t know your entire employment history right off the bat, but it personalized its form so you can customize your resume depending on the number and type of positions you hold or have held in the past.
Before dynamic forms, form fields were usually just static. Going back to the LinkedIn example, the page would’ve had to display several separate fields, making only the first one required.
Needless to say, this is an inefficient approach requiring a large number of form elements and repetition of code, which violates the DRY principle.
Now let’s see how dynamic forms are built in Vue.
To demonstrate, we’ll build a visa application form and add a “previous visa” field to it — many governments check information like this before issuing a visa. We’ll then make the app dynamic because, just as LinkedIn users have varying employment histories, different travelers have varying visas on their passport, leading to an infinite number of possible outcomes per user.
If you followed this article from the start, you should have a copy of the Vue canvas project from GitHub. You’ll see the root Vue component, app.vue
, and a test component file already created in the components folder.
Open the project in your integrated development environment (I use VS Code). Your app.vue
file should look like this:
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <Test/> </div> </template> <script> import Test from './components/Test.vue' export default { name: 'app', components: { Test } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
Now go inside the component folder, open the test component file, and copy the code block below inside the template section:
<template> <div id="visa"> <h1>Vue Visa Application</h1> <form> <label for="first name">First Name:</label> <input type="text" required> <br> <label for="last name">Last Name:</label> <input type="text" required> <br> <label for="country">Nationality:</label> <input type="text" required> <br> <label for="passport number">Passport Number:</label> <input type="text" required> <label for="duration">Duration of stay:</label> <input type="text" required> <br><br> <button>Add another previous visa</button> <br> <div class="previous"> <span>x</span> <label for="duration">Previous Visa:</label> <input type="text" required> <label for="duration">Year of expiration:</label> <input type="text" required> </div> </form> </div> </template>
This is a template for a fictional Vue visa application form with a button labeled “Add another previous visa,” which enables the user to enter all the valid visas on their passport.
In that same component, scroll down to the scoped style section and add the following rules:
<!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> #visa { margin: 20px auto; max-width: 700px; } label{ display: block; margin: 20px 0 10px; } input { font-size: 30px; border: 1px double rgb(102, 97, 96) ; border-radius: 4px; } button { font-size: 16px; background: rgb(64, 179, 140); padding: 0.4rem 1.3rem; text-align: center; border: none; cursor: pointer; border-radius: 4px; margin: 10px; } span{ width: 30px; float: right; cursor: pointer; } span:hover{ color: brown; } .previous{ border: 1.5px solid; padding:5px; margin-bottom: 10px; } </style>
Run your application in the dev server with the command below.
npm run serve
If you are running Node 17 or 18, you may get the following error: code: 'ERR_OSSL_EVP_UNSUPPORTED'
. To fix this you can downgrade to Node 16 using Node version manager or add the following flag to the serve script --openssl-legacy-provider
in package.json
so it reads "serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve"
.
This should work on Mac and Linux, setting environmental variables on Windows would look like this: "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve"
.
Your app should look like this in your browser at localhost:8080:
In a real-life scenario, a visa applicant might have more than one visa stamped on their passport, which is why we have the “add another previous visa” option for each applicant to add as many visas as they have. They can also add none if they are traveling for the first time.
Giving the user that power to choose is the best solution to a use case like this. Vue allows you to do that easily with a few directives: v-for
and v-bind
for the v-for
key. With the v-for
directive, you can make the previous visa block in the template reusable. This means you can duplicate a given code block and push it to the DOM with the click of a button instead of rewriting those lines over and over.
To do this, we have to create two functions: one to add a new section when we click the “add” button, and another to delete a section when the “x” button is clicked.
In your test component, copy the code block below inside the script section:
<script> export default { name: 'Test', props: { msg: String }, data(){ return { applicants:[ { previous: '', expiration:'' } ] } }, methods : { addVisa(){ this.applicants.push({ previous:'', expiration: '' }) }, deleteVisa(counter){ this.applicants.splice(counter,1); } } } </script>
A description of these methods:
addVisa()
adds a new object to the “applicants” array, each object in the array will be bound to the form elements in each set of inputsdeleteVisa()
is passed to the index of the input to be removed, then that object is removed from the arrayWe also created an applicants array, because we want to loop through various sections. To use these functions, we have to add them to the click events in the template section:
<template> <div id="visa"> <h1>Vue Visa Application</h1> <form> <label for="first name">First Name:</label> <input type="text" required> <br> <label for="last name">Last Name:</label> <input type="text" required> <br> <label for="country">Nationality:</label> <input type="text" required> <br> <label for="passport number">Passport Number:</label> <input type="text" required> <label for="duration">Duration of stay:</label> <input type="text" required> <br><br> <button @click="addVisa">Add another previous visa</button> <br> <div class="previous" v-for="(applicant, counter) in applicants" v-bind:key="counter"> <span @click="deleteVisa(counter)">x</span> <label for="duration">{{counter+1}}. Previous Visa:</label> <input type="text" v-model="applicant.previous" required> <label for="duration">Year of expiration:</label> <input type="text" v-model="applicant.expiration" required> </div> </form> </div> </template>
Things to notice:
The v-for
directive is used to loop through the applicant array, which is how we make sure the number of inputs is correct, because a set is created for each object in the array. The variable counter
will represent the index of the current object, and applicant
holds the current object.
Each div has an x
with a click event triggering the deleteVisa
function, and is passed into the index via the counter variable created by the v-for
directive.
Finally, each input is bound to the right property of the applicant
object using the v-model
directive.
If you run the application, it should add or remove the section, depending on the user’s action.
As you can see, the applicant has total control, and can delete the very first section if traveling for the first time. This would easily lead to a good customer experience.
The complete code to this tutorial is available on GitHub.
A few challenges with dynamic forms and how to think through solving them:
First, creating a variable number of inputs may make it tough to line up with the any backend routes that will receive their data. The simplest solution is that the data from each additional item the dynamic form represents gets added to an array which is sent to the backend.
Next, if there is a limit to how many items a user should be able to add, you may want to include another data variable to hold the number that currently exists, and use it for conditionals so they can’t add more when they hit the max.
Finally, validating a variable number of inputs can also be tricky. If you use an array to house the data in each additional set of inputs, you can then iterate on that array during any validation.
Now you should have some creative tricks up your sleeve to make Vue form elements dynamic using directives and a few functions. No matter what you want to build, you should always consider the customer first. This will help you empathize and build valuable products while maximizing efficiency, reusing code, and keeping it DRY.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
4 Replies to "How to make form elements dynamic in Vue.js"
HI there. Thanks for the tutorial. I tried it but it seems like the data binding isn’t working too well? Every time I input something in either the “previous visa” or “year of expiration” field, it gets repeated in the other generated fields as well.
you can do this way
Title
Description
File
Remove
Add row
export default{
name: “app”,
data: () => ({
rows: []
}),
methods: {
addRow: function() {
var elem = document.createElement(‘tr’);
this.rows.push({
title: “”,
description: “”,
});
},
removeElement: function(index) {
this.rows.splice(index, 1);
},
}
});
Thank you so muuuuuuuuuuuuuuuuuuuuuuuuuuchhhhhhhhhhhh!!!!!!!!!!!!!!, I have been trying to do this till found here
Wow. This helped my friend whose work as frontend. I’m just doing backend and helping him, then found your blog. Thank you very much.