Understanding Vue Mixins, and some practice with a vue-cli project
Mixins in Vue.JS occupy a bit of a grey-area of intermediate-to-advanced functionality. A bit component-like, decoupled from iterations of themselves, useful for maintaining DRY (don’t-repeat-yourself) code; but not enough available use-case distinction from state management, or even just the right filters, and with enough gotcha’s to make a lot of developers think twice about using them.
Note that I am referring, here, to mixins specifically in Vue/ React mixins, mixins for ES6 class syntax, etc., constitute a different beast altogether.
To shed some light on the utility and the limitations of Vue mixins, we’re going to put together a small demo. It’s my hope that this demo will paint a clear enough picture, with clear enough lines of demarcation implied between when you should (or can) or should not resort to a Vue mixin, while also providing some practice with getting out of the gate fast with a vue-cli project workflow.
Without further ado,
For this project, you’ll need the following:
- NodeJS — be sure to download the version labeled “Recommended for most users”
- npm — node package manager will install with the NodeJS installation above; feel free to use an alternate package manager if you feel so inclined
And we’ll be using (after we install them with npm):
- VueJS — we’ll use a number of vue-specific techniques and syntax which I’ll do my best to clarify as we go (though this isn’t a lesson in Vue, per se); if you’re unfamiliar with Vue, I’d strongly encourage you to check out their excellent docs
- Vue-cli — Vue’s command-line interface which will allow us to rapidly throw together a boilerplate Vue project
And that’s it!
To start, download and install NodeJS from the link above. Then, open up your favorite terminal window and navigate to the directory where you would like your project folder to be built.
Use the following command to install the vue-cli:
npm install -g @vue/cli
npm declares a node package manager command, install is the context-specific command we’re running, -g is a modifier which will cause our install to be a global one (across our entire machine, rather than this current directory), and @vue-cli is the package being installed.
This will allow you to initiate commands beginning with vue. Now to build our project folder, in the same terminal window, enter:
vue create vue-mixins-demo
This executes a vue command, create, which will construct a boilerplate project folder for a project titled “vue-mixins-demo.” Simply hit “enter” to select all defaults when it prompts you with a question or two during the creation process. You should now be able to see the “vue-mixins-demo” project folder in your current directory, and can open it in a text-editor or IDE of your choice.
Still in the same terminal window (don’t panic if you closed it, just open a new one and navigate to the same directory containing your project folder again), we navigate into our project folder with:
cd vue-mixins-demo
and follow that command with:
npm run serve
which will compile a hot-reloading development build for us. In other words, when this process is done, you should see something like this output in your terminal:
I can now open up a browser window and type in that local address, localhost:8080, to see my Vue app in action. Additionally, any changes we make in our code will be reflected in this live app immediately upon saving.
Now we’re all set up to begin writing some code.
What we’re going to be building is a small web app showing a fully sortable list of all users, as well as a list of active users and a list of inactive users (using dummy data, of course). The user data in question:
All the data has been generated randomly from the Random User API (more on that, and swapping static data for data fetched from an API, towards the end).
For starters, delete the HelloWorld component from the boilerplate code, and remove all reference of it from your App.vue file. In fact, replace all the content of your App.vue file with the following:
As you can see, we’ll be rendering three new components in our App view. Let’s create the files for all of our components now, so our application doesn’t crash while we make changes. Make AllUsers.vue, Active.vue, Inactive.vue, and User.vue files in your project’s components folder.
The User Component
The code for the User component will be as follows:
Currently, for convenience’s sake, I’ve included styling (this isn’t a CSS lesson). Take note of the “props” property in the script block, however. This is setting up this User component to receive some kind of data prop called “user” from its parent when it is rendered. Since we already know what a “user” object will look like, we can safely extrapolate what information we’ll extract from that object and how.
This is what informs the data binding in the template markup (“user.name”, “user.status”, etc.). Note that v-bind is the only vue directive on display here at the moment.
In the case of the div on line 7, its class attribute has been bound in such a way that the div will receive the class of “active” or of “inactive” based on the value of the “status” property of its “user” object (if status is 1, the user is active; if status is 0, the user is inactive).
This User component will display the information for exactly one user, and will be replicated to display multiple users.
The AllUsers Component
Now, let’s set up its parent component, AllUsers:
Once again, styling has been included for convenience (and be sure to paste the users array from above in place of this dummy array in the users property of the data object in this component!).
Aside from a containing div and an h2, all we have is an unordered-list containing one template of our User component (which will render a list-item with a single user’s information, remember), on which we’ve utilized the v-for directive to duplicate this component as many times as there is a “user” object in our “users” array in this component’s data. So we should end up with 12 User components, one for each user.
Note line 8, in which a “user” attribute is bound to the value of “user”. This can be confusing if you’re not familiar with Vue’s data-binding. The “:” indicates a v-bind directive, and the “user” immediately following (the attribute name) serves as the name of a data property which is being passed into this component. The “user” on the opposite side of the equals sign is the individual user object pulled from the “users” array. In this way, we pass a single, distinct user object as the “user” prop into each User component (whew). This is how the User component understands which object from which to pull a user’s name, age, status, and created information.
Sorting Users
Now you should have a functional application showing something like this:
So far so good, but let’s add some sorting functionality. For this, we’ll need some new markup, a method, and a few computed properties.
We’ve added clickable radio buttons whose values are all bound to properties shared by our user objects (name, age, status, created, image). A computed property, userProperties, extracts all the properties from a user object, and a v-for loop renders as many radio buttons and labels as userProperties extracts properties (with one v-if to ensure we don’t render a radio button labeled “image”, since it would be nonsensical to try to sort users by their image URL). The values of our radio buttons are also bound to a new data key, sortCriteria. If a user clicks on the “age” radio button, sortCriteria’s value will be set to “age”, etc.
We’ve also added a basic sort_by method which sorts an array by a given properties (e.g. alphabetically by name, or numerically by date).
Then there’s the sortedUsers computer property, which returns the array of our users sorted according to the new sortCriteria data point (which is kept empty if no radio button is checked), by calling the sort_by method and feeding in the users and sortCriteria data as arguments.
Most importantly, we have updated the v-for loop which renders our User components to pull from the sortedUsers data rather than the initial users array. In this way, the order of User components rendered on the page will react to any changes in the sortedUsers property, which will reactively change whenever any of the data it monitors undergo any changes (such as sortCriteria, when a user clicks on a radio button).
And just like that, we have a sortable list of users that should look something like this:
Active and Inactive Components
Now let’s create two new components, Active.vue and Inactive.vue, which will render lists of active versus inactive users (users with a status of 1 will be considered active, and users with a status of 0 will be considered inactive).
However, since we want the active and inactive user lists to be truncated somewhat, we won’t be rendering User components inside these lists. They will be more referential compared to the more comprehensive AllUsers list.
These components will look like this:
In addition to bringing the same users data, sort_by method, and sortedUsers computer property into this component, we’ve created a new data key called “status” which will be used to indicate whether the users to be featured in this component are active or inactive, and we’ve created a new method, filter_active_inactive which uses the “status” key as a filter mechanism for culling the users array down to ONLY the active users and we use the filtered returned users array as an argument in the sort_by method in this component’s sortedUsers property.
You can duplicate this component entirely, while switching the status property to 0 and the title from “Active Users” to “Inactive Users” to safely arrive at both Active and Inactive components.
So there’s some new stuff in here; at the same time, compared to AllUsers.vue, there’s A LOT of old stuff. There’s a lot of repeated code and even repeated functionality. This is where the advantage of mixins finally comes into play.
The Mixin, at last
What we can do with a mixin is reduce that repeated, redundant code, and dramatically clean up some of our components in the process. In essence, we’ll be decoupling the repeated data and functionality, though there will be a few gotchas to watch out for which I’ll discuss later.
In your project’s “src” folder, create a new folder called “mixins,” and inside this folder create a file called “filterUsers.js.” In this file, we’ll simply collect all the redundant code that’s repeated between our AllUsers, Active, and Inactive components:
(again, don’t forget to replace the shortened dummy users data with the full users array from above)
We’ve combined all the redundant code into one constant which we’re exporting. We can import this into our component files and merge all of this data with our component data without conflict of any kind.
Now let’s DRY up our other components’ code:
As you can see, all the repeated user data, the repeated methods and computer properties, have been completely removed (I even moved the userProperties computer property into the mixin file, even though it was only utilized by one component; functionally, it just made sense to me).
We then import { filterUsers } into each component which requires the functionality contained therein (AllUsers, Active, and Inactive), and simply declare it inside the Vue object our components are exporting with line 23 or line 35 in the files above, respectively (note, the “mixins” property of a Vue component will always contain an array!).
Our methods and computed properties in the mixin file contain if statements which will ensure the app does not throw an error if a certain piece of the puzzle is not present component-side (AllUsers does not contain a status property, and Active and Inactive do not contain a sortCriteria property; see lines 20 and 39 in the filterUsers.js file. In this way we’ve made our mixin code that much more resistant to unexpected bugs and that much more adaptable for use in a variety of component use-cases.
All three components which utilize this mixin are rendering content based on the same user data, and all three have access to the same methods and computed properties which would otherwise be repeated throughout our app and dramatically clutter up the component files themselves. And our app continues to work as expected without missing a beat!
Check out the full code here.
The Gotchas
As I mentioned, there are a few gotchas to keep in mind with Vue mixins.
The first, and most accessible, is that, when using mixins in your components, your components will always have the final say. What I mean is, your component’s own internal architecture will load AFTER the content of a mixin, which means your component will be equipped to over-write any data, methods, properties, or otherwise contained inside a mixin file. This makes perfect sense from just about any perspective, but I don’t want anyone making the mistake of thinking that incorporating a mixin to over-write component data in such-and-such use case will work. It won’t.
The second requires the clear understanding that, for every component a given mixin is merged with, a duplicate of that mixin code is created. While mixin’s allow us as developers to write less code, or to clean up some of our component files, it is not serving as a unified source of data for every component in which it is used. The code inside that mixin file is duplicated wherever you choose to insert it into your app.
This isn’t a problem in and of itself, of course, but certainly something critical to understand.
To put it more clearly in terms of the app we just built, let’s say you wanted to build a feature to change a user’s status from active to inactive inside the AllUsers list. That would be easy enough to do with a method which you could include either in the AllUsers component or inside the filterUsers mixin. This method would presumably change the status of a given user inside the users array in data, which would cause the sortedUsers computed property to update to reflect the new data. However, this updated data would only be reflected inside the component from which the method was executed. We would only see the change in a user’s status inside the AllUsers component. We would not see the user jump from the Inactive list to the Active list as you might expect.
The reason is because the users data has been duplicated everywhere the mixin was included. The users data in the Active and Inactive components will not reflect the change executed in AllUsers.
This is critical. To reflect the change elsewhere, some even would still need to be emitted or some state changed.
This is even more critical when considering fetching that user data from an API, instead of hard-coding it. That’s a perfectly viable (and even recommended) approach, but I should remind you that this user data was pulled from the Random User API. If we executed a fetch to the Random User API to retrieve twelve users wherever this mixin’s code was executed, that would constitute three distinct API calls.
We would be loading three entirely different lists of users. One for AllUsers, one for Active, and one for Inactive.
In reality, you wouldn’t normally be relying on randomized user data, so that’s not necessarily an issue; but you’d still be performing three API calls to receive the same identical information.
However, this is an easy-enough problem to solve. Remove users from the mixin altogether, and fold them (or a single API call to fetch them), into the parent view, App.vue, and simply pass them down to the three child components as a prop. Rewire the radio buttons in AllUsers.vue to emit an event which is listened for in the parent, and executes the update to the users list. That change will trickle downwards through any children who receive that data as a prop.
But that would require significant retooling of our mixin, or even render it almost entirely redundant itself.
Why don’t you give it a try and see?
So, can Vue mixins be useful? Absolutely, but they require some planning and understanding of their limitations. Are they the end-all, be-all of cool Vue features? Absolutely not, but it’s always helpful to know how to DRY up your code in a pinch.
Yet I would wager that more often than not, the need for a mixin indicates that there’s probably a more elegant or more robust solution out there.
Evan is an illustrator, developer, designer, and animator who tells stories in any which way he can. When he’s not branding businesses or building front-end apps; he’s illustrating children’s books, painting for tabletop games, animating commercials, or developing passion projects of his own.