DEV Community

loading...

Helpful patterns I use in Vue

Jordan Brennan
Building UI, attempting to learn ML
・3 min read

There’s some simple patterns I tend to follow in my Vue projects that I figure work well enough to share, so here ya go.

I’m really curious to see what other people do for these cases, so please share.

Loading state

I use a simple loading object of true/false flags that I toggle whenever that loading state changes. Messaging, loading indicators, and entire content sections are driven by these flags.

Even though there are cases where I could use the presence or absence of the relevant data, using a separate object provides the flexibility needed for any design requirement.

An example:

<template>
  <div>
    <div v-if="loading.users">Loading users</div>
    <table v-if="!loading.users">
      ...
    </table>
  </div>
</template>
<script>
export default {
  data() {
    return {
      users: [],
      loading: {users: false}
    }
  },

  created() {
    this.loading.users = true;

    fetch('/users')
      .then(users => this.users = users)
      .catch(console.error)
      .finally(() => this.loading.users = false)
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Error messaging

Similar to loading states, I set up a simple errors object and toggle flags. I’ve found error messages are best done in the template rather than in the errors object because one error can sometimes trigger multiple UI bits.

An example:

<template>
  <div>
    <div v-if="errors.fetchUsers">Failed to load users.</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      users: [],
      errors: {fetchUsers: false}
    }
  },

  created() {
    fetch('/users')
      .then(users => this.users = users)
      .catch(err => {
        this.errors.fetchUsers = true;
        console.error(err);
      })
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Occasionally a component needs to know if there are any errors at all. That's really easy to check for:

// Basic programmatic check
const hasErrors = Object.values(this.errors).some(err => err)

// Or as a computed
computed: {
  hasErrors: function () {
    return Object.values(this.errors).some(err => err)
  }
}
Enter fullscreen mode Exit fullscreen mode

Avoid Event Modifiers

From the docs:

<form v-on:submit.prevent="onSubmit"></form>
Enter fullscreen mode Exit fullscreen mode

That .prevent is a shortcut to the already short e.preventDefault(). The cost of proprietary markup like this scattered all over your app trumps their negligible convenience.

A strength Vue (and Riot) have is their plainness. That makes them easier to adopt and easier to replace. Using too much of a framework's special sauce increases the dependency - not good! And makes it harder for newcomers to understand your code as well.

Flatter component hierarchy

I avoid nesting Vue components beyond the third layer. The fourth layer is implemented as Custom Elements because I strongly prefer to write vanilla js any time there isn't a need for framework-specific features.

My Vue (and Riot) projects look like this:
Flatter component hierarchy
This is an ideal design I could never quite achieve with React because React struggles a bit with Custom Elements even though they are a web standard.

Shared modules

This one may be obvious, but I sometimes see over-engineered solutions for these kinds of problems so I figured it's worth sharing.

Instead of creating a Vue component or custom directive or other Vue-dependent solution, I strive to use simple Vue-free modules where possible. For example, in several components I need to format a raw number of bytes as KB, MB, GB, etc. I export a function that does this and import it in the components that need it:

// data-utils.js
// No Vue allowed!

export function formatBytes(bytes = 0) {
  // Zero is a valid value
  if (bytes <= 0) return '0 bytes';

  // Handle non-zero falsy
  if (!bytes) return '';

  // Format value, e.g. "5 MB"
  const k = 1024;
  const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB'];
  const size = Math.floor(Math.log(bytes) / Math.log(k));
  const num = parseFloat((bytes / Math.pow(k, size)).toFixed(2))
  return `${num} ${sizes[size]}`;
}
Enter fullscreen mode Exit fullscreen mode
<template>
  <div>
    <p>File size is {{ formatBytes(file.size) }}</p>
  </div>
</template>
<script>
import {formatBytes} from '../data-utils';

export default {
  data() {
    return {
      file: new File()
    }
  },

  methods: {
    formatBytes
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

I usually end up with a dozen or more of these and they're so much easier to write, use, and test when there's no dependency on Vue itself even though they're for a Vue project. Sometimes these end up graduating to a shared package that can be used by other projects because they're decoupled from Vue.


Beyond that I'm just using the basic features of Vue as-is and that alone takes me incredibly far. It's this straight-forwardness that makes Vue more powerful than other more ambitious and proprietary frameworks. Vue gives you so much more than it takes in my experience, and in many cases you don't have to let it take either.

Beyond the basics, what patterns are you using in Vue?

Discussion (18)

Collapse
padarom profile image
Christopher Mühl • Edited

Using too much of a framework's special sauce increases the dependency - not good!

I‘m not sure I agree with this statement. The purpose of a framework is to make certain development tasks easier. To make use of those you will pretty much always be required to write framework-specific code (if not, why would you be using the framework to begin with?) Of course it‘s debatable whether syntactic sugar is always worth the reduced clarity of intent, but I don‘t see the dependency on the framework to be the issue here.

I‘ve not had to convert a huge codebase from one framework to the other. Usually it‘s been complete rewrites, occassionally taking inspiration from the old code, but never could I just translate Vue into e.g. React just one-to-one without also having to change tiny details that are just too different between these frameworks anyways.

Other people‘s mileage may vary, but I think not making use of reasonable syntactic sugar due to fear of maybe having to translate it into a different framework at some point in the future will make your life harder, not easier. This is not to say that .prevent specifically is a worthwile feature, but there most certainly are other framework-specific features that would fit into this category.

Collapse
jfbrennan profile image
Jordan Brennan Author • Edited

I prefer composition, so when possible I take opportunities - even small ones like avoiding low-value template shortcuts - to flatten the dependency graph. This leaves as many pieces of the system as possible open to change or replacement.

Vue helps with structure and managing the render cycle - the biggest pieces of an app - but I don't want it doing much more than that for me. There's better options, usually vanilla js, for other parts of an app and I don't want to lock myself in or bloat my project by YOLO-ing the framework. Thankfully Vue is not like React or Angular or Ember in that way, so it's not possible to get that deep, but I avoid it just the same.

Collapse
bernardwiesner profile image
Bernard Wiesner • Edited

Could you elaborate more on the meaning of "I avoid nesting Vue components beyond the third layer."

I am not familiar with third or fourth layer, in fact I am not sure what the first and second layer are supposed to be, what do they refer to?

Collapse
jfbrennan profile image
Jordan Brennan Author • Edited

Typically Vue apps start with an app.vue component. It usually sets up a high-level layout for the product and routed views. That’s layer 1.

The app is then broken down into “views” which are mostly 1:1 with the logical pages of your product. GitHub, for example, would have repos list view, repo details view, user profile view, etc. These pages are layer 2.

Each page has some template code, state and other logic code, and maybe some styles of it’s own and it includes other components. Repo details page from the GitHub example would include components like files list, PRs, issues, settings, etc. These components are layer 3.

When I’m at layer 3 and I see a strong need for another layer of abstraction I go for simple Custom Elements, the M- library to be specific (I’m the creator).

Collapse
bernardwiesner profile image
Bernard Wiesner

Thanks for explaining. Just to clarify if you have:

app.js
search.vue
list.vue
favoriteButton.vue

You would implement the favorite button component using custom elements?

The favorite button is a button for adding the item on the search results list to your favorite items.

The same favorite button is also used on the detal page.

That would be considered a 4th layer?

Thread Thread
jfbrennan profile image
Jordan Brennan Author • Edited

Generally yes. However, if I take that example at face value then it seems like there’s no need for a fourth layer at all. The list component and the detail component both use a regular button and their click handler uses whatever API there is for adding an “item” to “favorites”. No compelling reason for an abstraction here and just because two event handlers call the same storage API doesn’t mean the code isn’t DRY either. That’s my simple approach anyway.

Thread Thread
bernardwiesner profile image
Bernard Wiesner

Well the button does a few things.

A star on the button changes color depending on if the item is already in the favorite list, so have to keep that state.

If the user is not logged in, a popup is shown, asking the user if he wants to login.

So having it as a component is convenient, cause it's easy to reuse. I am not sure what would be the advantage of using custom elements.

Thread Thread
jfbrennan profile image
Jordan Brennan Author • Edited

Ah, then yeah I'd go CE.

There's no huge must-have advantage in doing this, I just mostly prefer to use vanilla js when possible. The minor advantage in this case being one more little piece of the app is pure dependency-free js, which means the app is just a teeny weeny bit smaller and faster and this CE could be possibly be used with a non-Vue project should the need arise. The big gains come as these little components (and the plain js modules I mentioned as well) add up over time as the project scales. If there's 20, 30, or more of these vanilla components you're saving real bytes and overhead and a good portion of your codebase is emancipated.

Definitely not ground-breaking stuff, but I'll take these little wins where I can, especially if it means more vanilla code.

Thread Thread
bernardwiesner profile image
Bernard Wiesner

Thanks, seems interesting to look more into CE

Collapse
morficus profile image
Maurice Williams

It is virtually impossible to avoid how deep components are nested the second you use any 3rd party component or library. Popular libraries like Vuetify and Quasar all have componets that are more than 3 levels deep.

Collapse
jfbrennan profile image
Jordan Brennan Author

I use my own UI library of custom HTML tags and Custom Elements, which has been really good.

Collapse
momander profile image
Martin Omander

Excellent article - thanks for sharing! You inspired me to start using reactive objects for loading state and errors.
I agree fully about putting non-UI utility functions in plain Javascript files. I usually drop such functions into a file named Util.js. Being vanilla Javascript and pure functions, it's easy to bring parts of that file over to new projects as needed.

Collapse
jfbrennan profile image
Jordan Brennan Author

Yeah it’s so handy when you’ve got a chunk of vanilla js you can just freely use when and where you want to take it

Collapse
morficus profile image
Maurice Williams

Using too much of a framework's special sauce increases the dependency - not good!

But what makes a framework worth using is its "special sauce". That's what makes building rhings easier or faster or safer. If your goal is to avoid using the tools that the framework provides ... Then why use the framework at all?

Collapse
jfbrennan profile image
Jordan Brennan Author

True, but didn’t I say “too much special sauce”?

I use Vue for application structure and managing the render cycle when state changes. Those are the two primary benefits Vue offers. Yes, like other frameworks, Vue can do more than that, but I don’t just go all in because I’m using a given framework. I like to keep things flat and vanilla when there isn’t a compelling reason not to. And I like the freedom to use other libraries that are better than what the framework offers (things like dates, i18n, UI library)

Collapse
marktnoonan profile image
Mark Noonan

In terms of the loading and error states, it can sometimes get a bit clunky with all the booleans, and it’s nice to have a lookup for the single state that applies to something - eg, it can be in an error state, a loading state, a resolved state, whatever, but only one at a time. Then in your catch you change something like this.state.users from “loading” to “error” and otherwise from “loading” to “resolved”.

Love the others, especially the custom element idea. Kinda want to look into that. Thanks for sharing.

Collapse
jfbrennan profile image
Jordan Brennan Author

Ok that’s cool. I think…so you kind of define an enum of states 'error', 'loading', etc. and assign one to the things that are currently in that state? I like how that could work for stuff that kind of goes through a process of state changes loading —> error —> resolved, but I don’t think it works for granular errors like input validation of a large form.

Thanks for sharing!

Collapse
vladi160 profile image
vladi160

loading = null/true/false to catch the first page/component rendering