DEV Community

Cover image for VueUse as must-have library for Vue 3
Roman Garmider 💛💙
Roman Garmider 💛💙

Posted on • Edited on

VueUse as must-have library for Vue 3

For those who are unfamiliar with this library, I advise you to try it, as it can de facto become the standard for use in Vue 3 projects, as, for example, once there was a lodash library for almost any js projects.
Others have probably already checked out all the extensive functionality that it provides. Some have already used it in Vue 2, but not all new features support the old version. The library's arsenal is impressive, there are simple utilities like getting mouse coordinates, and various complex integrations with Firebase, Axios, Cookies, QR, local storage, browser, RxJS, animation, geolocation, extensions for standard Vue hooks, a media player and much more. Evan You himself is noted among the sponsors of the library, which seems to be a good sign. The library receives regular updates, bug fixes, and the community grows. So, it has everything for success.
In this article I will only talk about 4 features, but, of course, all the others need attention.

onClickOutside - clicks outside the element

I'm sure you can handle the installation of the library by yourself, so let's go straight to the interesting features. To warm up, consider a simple hook that tracks clicks outside a given element - onClickOutside. There are many packages that provide this functionality, and almost everyone has probably written that function by themselves. Usually it is implemented by adding a custom Vue directive to the desired element, for example v-clickOutside, but the use of a hook is unusual.
I used this hook in my todo app, in the ToDoItem.vue component:

<template>
  <li ref="todoItem">
    <input type="checkbox" />
    <span v-if="!editable" @click="editable = !editable">
      {{ todo.text ? todo.text : "Click to edit Todo" }}
    </span>
    <input
      v-else
      type="text"
      :value="todo.text"
      @keyup.enter="editable = !editable"
    />
  </li>
</template>

<script lang="ts">
  import { defineComponent, PropType, ref } from "vue"
  import ToDo from "@/models/ToDoModel"
  import { onClickOutside } from "@vueuse/core"

  export default defineComponent({
    name: "TodoItem",
    props: {
      todo: {
        type: Object as PropType<ToDo>,
        required: true
      }
    },
    setup() {
      const todoItem = ref(null)
      const editable = ref(false)

      onClickOutside(todoItem, () => {
        editable.value = false
      })

      return { todoItem, editable }
    }
  })
</script>
Enter fullscreen mode Exit fullscreen mode

I removed the extra code to avoid distraction, but the component is still large enough. Pay attention to the code inside the setup hook, first we create an empty todoItem link, which we hang on the desired element in the template, and then we pass the first parameter to the onClickOutside hook, and the second parameter is a callback with the actions we need. When you click on the span tag, it will be replaced with an input tag, and if you click outside the li tag with the ref="todoItem" attribute, then the input will be replaced with a span tag.

useStorage and createGlobalState - reactive local storage

The next function I'll talk about is useStorage. This function allows data to be stored in Window.localStorage. It is convenient to use it in conjunction with createGlobalState, which is used to create a global storage. Now the data will be saved, updated and deleted automatically, and will not disappear after the page is reloaded. Below is the example of using these functions:

// @/store/index.ts
import { createGlobalState, useStorage } from '@vueuse/core'
import Note from '@/models/NoteModel'

// state
export const useGlobalNotes = createGlobalState(
  () => useStorage('my-notes', [] as Note[]),
)

// actions

const notes = useGlobalNotes() // for local use

export const addNote = function (note) {
  notes.value.push(note)
}

export const deleteGlobalNote = function (noteId: number) {
  notes.value = notes.value.filter(note => note.id != noteId)
}
Enter fullscreen mode Exit fullscreen mode

The first parameter of the useStorage function accepts a key under which it will save your data in localStorage, and the second is initial value. createGlobalState creates a wrapper function to pass state to components. By calling this function (in our case it is useGlobalNotes()) in Vue-components or right here in this file, we will get a reactive list of notes. The notes array can be used as usual, remembering that since this is a proxy object, and the list itself is stored in notes.value. No need to add .value to markup templates in components.
For comparison, it's also helpful to see the useStorage example from the authors of the library. The difference is that in setup, you need to work with reactive storage not directly, but through its value property. In the html template, everything is as usual.

useRefHistory - history of changes

useRefHistory is a hook that will record the history of data changes and provide undo/redo functionality. I used it to create the Undo and Redo buttons on the note editing page. First, I created a reactive variable using ref. Let's take a closer look at the code:

<!--  Note.vue -->
<template>
  <!--  ...  -->
  <div>
    <button type="button" @click="undo" :disabled="!canUndo">Undo</button>
    <button type="button" @click="redo" :disabled="!canRedo">Redo</button>
  </div>
  <!--  ... -->
</template>

<script lang="ts">
    import { defineComponent } from "vue"
    import { useRefHistory } from "@vueuse/core"
    import ToDo from '@/models/ToDoModel'

    export default defineComponent({
      setup() {
        const note = ref({
                title: "",
                todos: [] as ToDo[]
              })
        const {
            undo,
            redo,
            canUndo,
            canRedo,
            clear
            } = useRefHistory(note, { deep: true })

        const updateTitle = (title: string) => {
            note.value.title = title
        }

        const addNewTodo = () => {
            note.value.todos.push({} as ToDo)
        }

        const onRemoveTodo = (index: number) => {
            note.value.todos.splice(index, 1)
        }

        return {
            note,
            addNewTodo,
            onRemoveTodo,
            updateTitle,
            undo,
            redo,
            canUndo,
            canRedo,
            clear
        }
    },
  })
</script>
Enter fullscreen mode Exit fullscreen mode

We create a reactive variable using ref, pass it to the useRefHistory hook, denote deep: true in the hook parameters, for nested objects. Using destructuring assignment from useRefHistory we get history, undo, redo, canUndo, canRedo and clear. The canUndo and canRedo properties hang on the disabled attributes in buttons. clear - needed to clear history after finishing editing records. The useManualRefHistory hook does almost the same thing, but saving to history occurs only when the commit() command is called.

Conclusion

I have covered only 4 functions from the large arsenal of VueUse tools for Vue 3 development. For a more in-depth study, I advise you to visit the site of this wonderful library. While the documentation could still be improved, it is regularly being updated as well as the library itself.
The complete code of my polygon, where I tested this library, can be viewed here.

Top comments (10)

Collapse
 
jonghunbok profile image
박종훈

Hi, Roman!
I'm Jonghun Park, a front-end developer At Korean FE Article Team.
It was a very good article and I enjoyed reading very much!
Especially I think the 4 functions you picked really helps showing the productivity boost 'vueuse' can offer.
I'd love to translate the article into Korean and post it on Korean FE Article mailing substack(kofearticle.substack.com/), if it's okay with you.

Korean FE Article is an activity that picks good FE articles every week, translates them into Korean, and shares them.
It is not used commercially, and the source will be identified.

Please think about it, and let me know!
Thank you for a great article!

Collapse
 
garmideroman profile image
Roman Garmider 💛💙

Hi, Jonghun!
I'm glad you picked my article!
Of course, I'm ok with it.

Collapse
 
eshimischi profile image
eshimischi

Guy, who created this library is actually from the Core team of Vue, his personal projects are great, lots of useful things, became his supporter on Github, appreciate his work very much indeed.

Collapse
 
garmideroman profile image
Roman Garmider 💛💙

Agree. I also like his library Vue-Demi, which allows you to write plugins for versions 2 and 3 of Vue.js.

Collapse
 
eshimischi profile image
eshimischi

Lots of Vite plugins, vitesse, to name a few..

Thread Thread
 
garmideroman profile image
Roman Garmider 💛💙

Thx! Vitesse seems to be a really interesting starter template.

Collapse
 
pawelmiczka profile image
Paweł Miczka

Yes, I agree that it is must have library. I am using it right now in any project. Since it is "treeshakable" you don't need to worry about bundle size (so much).

Collapse
 
wheatjs profile image
wheat

Nice article! We also have have a renderless components library for a lot of our functions that you might be interested in vueuse.org/guide/components.html

Collapse
 
garmideroman profile image
Roman Garmider 💛💙

Interesting solution onClickOutside as a component. Why isn't there a list of all available components on the docs? I have not found them.

Collapse
 
wheatjs profile image
wheat

We are currently in the process of redesigning the docs, we should have a better way to discover these features soon :)