Are you using prop types to their full potential?
I know I'm not.
It's a part of Vue that's just glossed over.
But there's a ton of powerful stuff you can do with them.
So I collected 5 tips on getting the most out of prop types:
Update: I also wrote a tip on stealing prop types.
We'll start with the most basic one:
Use prop types in the first place!
Prop types are a really simple way to catch a lot of potential errors.
They let you describe what types of values your component is expecting for each prop. This reduces the chances of someone using your component the wrong way.
For example, let's use this ShoppingCart
the wrong way:
<ShoppingCart total="This is wrong" />
If our ShoppingCart
component accepts total
as a Number
, we want to prevent someone from passing something other than a Number
. We can do this by adding in a prop type:
export default {name: 'ShoppingCart',props: {total: Number,}}
Now if we try to pass in a string, or anything other than a Number
, Vue will give us a warning.
We can do far more interesting things to validate props though.
Let's say we've got an Image
component, and we can set the style of the image to be one of:
square
rounded
circle
We'll use it like this:
<Imagesrc="this/cool/image.png"style="square"/>
And we'll add in prop types for both props, src
and style
, to make sure that only strings are passed in:
export default {name: 'Image',props: {src: String,style: String,}};
But while this prevents us from passing in a Number
(or anything other than a string), we can still pass in whatever string we want to:
<Imagesrc="this/cool/image.png"style="rectangle"/>
Luckily, we can use a validator
in our prop types. This is a function that takes in the prop and returns true
or false
if the prop is valid.
It looks something like this:
(prop) => return propIsValid;
So let's get started adding in this extra validation!
First we have to change our prop types to use an object, so we can add more details to it:
export default {name: 'Image',props: {src: {type: String,},style: {type: String,}}};
Then we'll add in the validator function for the style
prop:
export default {name: 'Image',props: {src: {type: String,},style: {type: String,validator: (prop) => ['square','rounded','circle',].includes(prop)}}};
The validator function is just this piece (formatted to fit better):
(prop) => ['square', 'rounded', 'circle'].includes(prop)
Now if you tried to pass in something other than what's in that list, Vue will give you a warning.
In a similar way, we can use a validator
function to verify the shape of an Array or Object.
Here we have a simple TagBox
component, which let's us add and remove tags for our blog posts:
<TagBox :tags="['javascript', 'frontend', 'css', 'vue']" />
The prop tags
lets us pass in an array of strings, which each represent a single tag.
With the type
property we can make sure that the tags
prop is always an array:
export default {name: 'TagBox',props: {tags: {type: Array,}}};
But in our TagBox
component we also want to verify that each element of the array is a string. We don't want people passing in random objects, numbers, and other things.
How can we do this?
Well, we'll have to get a little creative with using Array methods in our validator
function:
export default {name: 'TagBox',props: {tags: {type: Array,validator: (prop) => prop.every(e => typeof e === 'string'),}}};
The array method every
runs a function against every single element in the array. It will return true
only if every function returns true
.
The function we run against each element is checking to see that each element is a string:
(e) => typeof e === 'string'
We could just as easily have checked that they were all numbers, dates, or any other type.
But lets take this one step further.
What if we want a minimum length for each tag?
We want to prevent someone from displaying super short tags with a
and it
, which will probably just look weird:
export default {name: 'TagBox',props: {tags: {type: Array,validator: (prop) => prop.every(e =>typeof e === 'string' &&e.length > 3),}}};
Now our validator
function looks like this:
(prop) => prop.every(e =>typeof e === 'string' &&e.length > 3)
I just added in an extra condition to check the length of the string.
And it doesn't have to stop there.
You can take this as far as you want, checking all sorts of things about the elements and objects that are passed in as props.
However, if you start doing things much more complex than this, you'll probably want to use a library. There are a few out there, such as vue-types and prop-types.
Along with adding types to your props, you can also specify if the prop is required or not:
export default {name: 'BirthdayBanner',props: {birthday: {type: Date,required: true,}}}
As a rule, if a prop isn't required, it should have a default value.
If you aren't requiring the user to provide a value, and you aren't providing a value either, then who is?
// The user *has* to provide a value, so we are safeprops: {birthday: {type: Date,required: true,}}
// The user may not provide a value, so we should just in caseprops: {birthday: {type: Date,default: new Date('July 10, 1856'),}}
The opposite is also true.
If you are requiring the user to provide a value, don't provide a default.
This default value will never be used, and will only cause confusion for those looking at your code later on. Best to keep it clean and remove anything that isn't necessary.
// So... the birthday will start out as July 10, 1856, right?// Wait, whoever is using the component is providing the birthday.// Why is that value even there then?// Now I'm just confused... halp.export default {name: 'BirthdayBanner',props: {birthday: {type: Date,required: true,// This value is never used, so get rid of it!default: new Date('July 10, 1856'),}}}
Here's something you may not have known:
You can have multiple types on your prop definitions:
export default {name: 'Counter',props: {value: {type: [String, Number],}}}
This component will accept both strings and numbers as valid types, but nothing else.
While this is possible, I wouldn't recommend it in most cases. Keeping your types stricter will keep your code more focused, and leave less room for bugs to come up.
For example, if you're allowing this prop to be either a string or a number, you need to handle both of those cases. This means you'll have to check to see if it's a string, and then convert it to a number first.
This Counter
component would probably look more like this:
<template><div class="counter">{{ count }}</div></template>
export default {name: 'Counter',props: {value: {type: [String, Number],}},computed: {count() {if (typeof this.value === 'number') {return parseInt(this.value, 10);}return this.value;}}}
Instead, if you just accepted numbers, you greatly simplify the logic of the component. You can get rid of all the extra logic:
<template><div class="counter">{{ value }}</div></template>
export default {name: 'Counter',props: {value: {type: Number,}},}
Although this is a just a toy example, I think it illustrates the point.
Any real components you're writing will likely be more complex. The headaches from dealing with this will also be worse.
If you learned something from this article, please take the time to share it so others can learn as well!
It also encourages me to keep writing more stuff like this 😊