Ramp up and "un-spaghetti"
A Vue.js app started getting out of hand, so I joined the frontend team to help refactor and follow best practices. I quickly learned the codebase, learned Vue.js and the Vuetify framework, and then started adding new features while reducing lines of code.
I didn’t have any experience with Vue.js prior to this project, but I was well-versed in other frameworks like React, Angular, KnockoutJS, Backbone and so on. It was easy to jump into Vue and find my way around, it’s very similar to React, uses the flux architecture with Vuex. Except, our frontend dev wasn’t using flux in most cases.
On a complex screen of the app, where using Vuex would’ve made the code much more simple and reliable, we had an ad-hoc global object basically doing the same job Vuex would’ve done, but doing it poorly and not enforcing one-way data flow. I had to fight with my team member for a little bit to get him to understand why we should use Vuex, but he came around and we were all a lot happier at the end.
He thanked me many times for forcing him to do the job right and helping him become a better developer. We worked on the app together very smoothly to add new screens and features, and I watched his coding abilities mature.
Using Flux pattern correctly
When to use flux pattern and when to maintain state locally?
I write the code so that components on a screen, or a few tightly coupled components, manage state themselves, by passing props and emiting events. By managing their own internal state, these components have no knowledge of the surrounding code and are comlpetely reusable by different screens. For example, the search bar, date picker, filter bar… we can use the same filter bar everywhere in the app, now. The parent view configures the filter bar for that view, then inside the filter bar, the local state tracks which options are selected for each filter, shows the selected options in a row with close icons to cancel the filter, disables selecting duplicate options, etc. The view doesn’t know anything about this internal state of the filter bar and the filter bar doesn’t know anything about the view.
The view does not manage it’s own state, it uses Vuex. So when a filter is selected or deselected, it emits an event that the parent view uses to update Vuex. Now the view doesn’t need to track which filters are selected. Then, when a query is submitted, the view calls the Vuex action, which checks the state for which filters to apply.
In this way, reusable components can be managed by a view to allow complex state, without any code becoming too complicated. Each module of the code does it’s own job:
- The view instantiating components and communicating with Vuex
- The components rendering themselves correctly and managing their own state
- The store (Vuex) managing the state of the page and interaction with external services
Don’t overwrite the CSS theme
When I came to the app, there was a mess of CSS rules overwriting
the Vuetify theme with
!important sprinkled in everywhere. It was
difficult to get this house-ofcards CSS to work properly, and hard to
even read the code and understand the intention of the CSS.
We made a new rule: no overwriting the Vuetify theme.
The options were:
- Be happy with the styling as provided by Vuetify
- Use the Vuetify helper attributes and classes to modify the style
- Extend the component to create a new styling option
- Write new CSS, but make sure it’s local to the component you’re creating
We define our own “Material Dark” theme in src/styles/variables.scss.
If any style in the app doesn’t look correct or needs to be changed to fit the spec/mockups, then the first place to try is to update the Sass variables. We prefer to update Sass variable to maintain Vuetify’s Material Design implementation.
If updating the variables doesn’t work for the style we want to create, then we can add CSS to variables.scss to overwrite the theme’s CSS.
If modifying the theme CSS doesn’t work for us, then we can write component-level CSS.
IMPORTANT: when writing component-level CSS, it is extremely important that the CSS is local to the component (create a new CSS class specific to the component). Component-level CSS should not override the Vuetify theme in any way that will affect other components in the app.
A lot of Vuetify components come with a choice of several styles
(“flat”, “solo”, etc.), but sometimes these don’t provide what we
need. No problem, we can extend the Vuetify component and create our
own style, and then we don’t need to complicate our app with
When to wrap vs extend Vuetify components
…if the Vuetify component does what we need, but we want to use it with some defaults or we want to use it to implement functionality that we will then re-use throughout our app, then we will create our own component that uses the normal Vuetify component. For example, with the DatePicker, we are basically configuring the Vue DatePicker for our use case and wrapping it with functionality to create the GroupSense DatePicker that we will use everywhere.
…if the Vuetify component does not offer what we need and we find ourselves fighting with the component to get it to do what we want, then we extend the component. For example, the Vue TextInput comes with a few choices for styling, but we needed an additional styling option for the DatePicker. No available configuration of the TextInput does what we want, so we’d need to resort to overriding the Vuetify CSS to get the style we’re looking for, but that would lead to problems when we DO want to use one of the default styles, so in the case of TextInput, it made sense to extend the TextInput component to include an additional styling option.
I'm working on a long-form article describing this project, complete with code samples, code review and screenshots. If you'd like to access an early draft of the content, enter your email below and I will get in touch.