Handling value-less HTML attributes as props in Vue

Handling value-less HTML attributes as props in Vue

19 Jul 2021 vue

I thought I'd write this because this gotcha comes up on Stack Overflow quite often.

TL;DR: Non-bound Vue props are passed as strings, and are not cast to other types. So the prop foo=true passes the string "true", not the boolean true.

I'm writing this in the context of Vue, as I'm a Vue guy, but the same gotcha applies to other frameworks.

The setup 🔗

Say we have an app with a single child component in it, which outputs a checkbox.

HTML:

<div id=app> <checkbox /> </div>

JS:

//child component Vue.component('checkbox', { template: `<input type=checkbox>` }); //Vue instance new Vue({ el: '#app' });

Now, suppose we want the initial checked state of the checkbox to depend on a prop passed from the master component. We might be tempted to do this:

<checkbox checked=false />

And we'd of course have to notify the child component of the incoming prop:

Vue.component('checkbox', { props: ['checked'], template: `<input type=checkbox :checked=checked >` });

The problem 🔗

When we run this code, we'll find that the checkbox always starts checked, regardless of whether we pass "false" or "true" (or anything else) as the prop value.

There's two factors in understanding why this is the case:

  • checked is a value-less HTML attribute; that is, it doesn't expect any value, and its mere presence implies true (checked). (Read more on MDN.)
  • Vue props, unless they are "bound", are always passed as strings.

So we're actually passing the string "false" as our prop value, not the boolean false, and non-empty strings always evaluate to truthy.

Now, we've told our child component that the checked attribute is bound to the incoming checked prop. Vue will ignore any incoming props that have falsy values. But as we've seen, it's impossible to pass boolean false in the way we're currently doing it - the value is passed as a string.

The solution 🔗

The solution is to bind the prop reference via v-bind: (or simply : for short). This is our gateway to passing any kind of data as the prop value, not just a literal string.

Let's modify the app template:

<div id=app> <checkbox :checked=isChecked /> </div>

And then in our parent component data, we'll create an isChecked member in our data object:

new Vue({ el: '#app', data: { isChecked: false } });

Now the checkbox output by the child component really will start unchecked!

Actually we don't even need to add a member to data (though this is more explicit and readable); simply binding the prop makes it dynamic, meaning whatever value we pass will be evaluated. So :prop=true really will result in a boolean being passed.

With this, you can see how data bindings are useful for more than just those cases where data may change; here, we may never programmatically change the value of isChecked, but the binding is still useful for passing non-string values to props.

Try the Fiddle, or read more on this in the Vue docs: Passing Static or Dynamic Props.

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!