Do it with Elegance: How to Create Data-Driven User Interfaces in Vue

Although we usually know what components are needed when building most views within an app, sometimes we don't know what they are until runtime. This means we need to build a screen based on the application state, user preferences, or response from an API. A common case is building dynamic forms, where the questions and components needed are either configured by a JSON object, or where fields change based on the users' answers.

All modern JavaScript frameworks have ways of handling dynamic components. This blog post will show you how to do it in Vue.JS, which provides a remarkably elegant and easy solution to the above scenario.

Once you see how easy this can be using Vue.JS, you might get inspired and see applications for dynamic components that you have never considered before!

We need to walk before we can run, so first I'll go over the basics of dynamic components, and then dive into how these concepts can be used to build your own dynamic form builder.

At this point, you might be thinking, “Well, so what? That’s handy - but I could have used v-if just as easily.”

This example starts to shine when you realize that <component> works just like any other component, and it can be used in combination with things like v-for for iterating over a collection, or making the :is bindable to an input prop, data prop, or computed property.

What about the props and events?

Components don’t live in isolation - they need a way to communicate with the world around them. With Vue, this is done with props and events.

You can specify property and event bindings on a dynamic component the same way as any other component, and if the component that gets loaded does not need that property, Vue will not complain about unknown attributes or properties.

Let's modify our components to display a greeting. One will accept just firstName and lastName, while another will accept firstName, lastName and title.

For the events, we will add a button in DynamicOne that will emit an event called ‘upperCase’, and in DynamicTwo, a button that will emit an event ‘lowerCase’.

Not every property or event needs to be defined on the dynamic component that we are switching between.

Do you need to know the props up front?

At this point, you might be wondering, "If the components are dynamic, and not every component needs to know every possible prop - do I need to know the props up front, and declare them in the template?"

Thankfully, the answer is no. Vue provides a shortcut, where you can bind all the keys of an object to props of the component using v-bind.

Data Binding

If a form is generated but does not bind data, is it very useful? Probably not. We currently are generating a form but have no means of binding data to it. Your first instinct might be to add a value property to the schema, and in the components use v-model like so:

There are a few potential pitfalls with this approach, but the one that we care about most is one that Vue will give us an error/warning about:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
found in
---> <TextInput> at src/components/v4/TextInput.vue
<FormsDemo> at src/components/DemoFour.vue
<App> at src/App.vue
<Root>

While Vue does provide helpers to make two-way binding of component state easier, the framework still prefers a one-way data flow. We have tried to mutate the parent's data directly within our component, so Vue is warning us about that.

Notice how we are using v-model="formData[field.name]". We need to provide an object on the data property for this:

export default {
data() {
return {
formData: {
firstName: 'Evan'
},
}

We can leave the object empty, or if we have some initial field values that we want to set up, we could specify them here.

Now that we have gone over generating a form, it’s starting to become apparent that this component is taking on quite a bit of responsibility. While this is not complicated code, it would be nice if the form generator itself was a reusable component.

Making the Generator Reusable

For this form generator, we will want to pass the schema to it as a prop and be able to have data-binding set up between the components.

Since the formData property does not know every possible field that we could pass in, we want to use this.$set so Vue's reactive system can keep track of any changes, and allow the FormGenerator component to keep track of its own internal state.

So now that you've seen how a form generator can leverage the basics of dynamic components in Vue to create some highly dynamic, data-driven UIs, I encourage you to play around with this example code on GitHub, or expirement on CodeSandbox. And feel free to reach out if you have any questions or want to talk shop - Twitter, Github, or Contact Rangle.io.