skip to content

What is VueUse, and what does calling its useEventListener or useIntersectionObserver in a Vue 3 component save you from writing by hand?

level: juniorimportance: must knowfreq 34%

answer

  1. a library of ready-made composables
  2. the removal you forget to write
  3. template refs are null during setup
  4. window is missing on the server
  5. stop handles and isSupported

basics

~20 s

VueUse is a collection of ready-made Vue 3 composables for browser APIs, sensors and reactive state. useEventListener and useIntersectionObserver attach their listener or observer, follow reactive targets, and remove it automatically when the component's scope is disposed.

solid answer

~40 s

VueUse (`@vueuse/core`) is a large set of tree-shakable composables that wrap browser APIs, sensors and reactive utilities in the Composition API style: you call them in `setup` and get back refs and control functions. Hand-writing `addEventListener` in `onMounted` means remembering `removeEventListener` in `onUnmounted`, coping with a template ref that is still `null` during setup, re-attaching when that element is swapped, and guarding `window` for server rendering. `useEventListener` handles all of that: it defaults to `window`, accepts refs as targets and re-registers when they change, and removes listeners when the component's effect scope is disposed; it also returns a stop function. `useIntersectionObserver` does the same for an `IntersectionObserver` and returns `isSupported`, `isActive`, `pause`, `resume` and `stop`.

code

vue · 27 lines
vue
<script setup lang="ts">
import { shallowRef, useTemplateRef } from 'vue'
import { useEventListener, useIntersectionObserver } from '@vueuse/core'

const emit = defineEmits<{ refresh: [] }>()
const panel = useTemplateRef<HTMLElement>('panel')
const chartVisible = shallowRef(false)

// on window by default; removed when the component unmounts
useEventListener('keydown', (e) => {
  if (e.altKey && e.key === 'r') emit('refresh')
})

// load the heavy chart the first time the panel scrolls into view
const { stop } = useIntersectionObserver(panel, ([entry]) => {
  if (entry?.isIntersecting) {
    chartVisible.value = true
    stop()
  }
})
</script>

<template>
  <section ref="panel">
    <RevenueChart v-if="chartVisible" />
  </section>
</template>

go deeper

for a junior

Recall that VueUse is a library of ready-made composables returning refs, and that its listeners and observers clean up when the component unmounts.

for a middle

Explain the hand-written bugs useEventListener avoids: forgotten removal, null template refs, swapped elements and missing window on the server.

for a senior

Judge where a VueUse function earns its place: lifecycle, reactivity and browser-support edges justify it; a one-line utility you already test does not.

for a principal

Treat VueUse as a shared vocabulary: standardising on its composables removes a class of leak bugs, but pin its major version because defaults change.

## What VueUse is **VueUse** is a collection of composition utilities for Vue 3, published mainly as `@vueuse/core` (with `@vueuse/shared` holding the lower-level helpers). Each export is an independent, tree-shakable function — `useEventListener`, `useWindowSize`, `useStorage`, `refDebounced` and hundreds of others — that you call inside `setup()` or `<script setup>`. Most return **refs** or an object of refs, so the result plugs straight into templates, `computed` and `watch`. VueUse 15 requires Vue 3.5 and ships as ESM only. Interviewers rarely ask for a list of its functions; they ask whether you recognise the bugs a hand-written version carries and reach for the tested one instead. ## The hand-written version and its bugs In an analytics dashboard, a panel that listens for a keyboard shortcut and lazy-loads a heavy chart when it scrolls into view might be written by hand like this: attach listeners in `onMounted`, create an observer, and tear both down in `onUnmounted`. Typical defects: 1. **Forgotten removal.** A missing `removeEventListener` keeps the handler — and the component it closes over — alive after the panel is gone. 2. **Null template refs.** During `setup` a template ref is still `null`; code that reads it too early silently observes nothing. 3. **Swapped elements.** If a `v-if` replaces the element, the listener stays on the old node and the new one is never observed. 4. **Server rendering.** Touching `window` or `document` at setup time throws on the server, where neither exists. 5. **Missing APIs.** Older or embedded browsers may lack `IntersectionObserver`, and hand-written code often crashes instead of degrading. ## What useEventListener does - **Default target:** `useEventListener('resize', handler)` attaches to `window`; pass a target first for anything else: `useEventListener(document, 'visibilitychange', handler)`. - **Reactive target:** the target may be a ref or a template ref. When it changes, the old listener is removed and a new one attached; while it is `null`, nothing is attached. - **Arrays:** several events, targets or listeners can be passed as arrays. - **Automatic cleanup:** registration runs inside a watcher owned by the component's effect scope, so when the component unmounts the watcher stops and every listener is removed. - **Manual cleanup:** it returns a function; calling it unregisters the listeners early. - **Server safety:** VueUse's default `window` is `undefined` on the server, so a listener with no explicit target is simply not attached there. An explicit `document` argument is still evaluated, so wrap such calls in `onMounted` for server-rendered pages. ## What useIntersectionObserver does | Returned control | Type | Meaning | |---|---|---| | `isSupported` | computed boolean | whether `IntersectionObserver` exists in this window | | `isActive` | ref | whether the observer is running | | `pause()` | function | disconnect for now and set `isActive` to false | | `resume()` | function | start observing again | | `stop()` | function | disconnect permanently | The target may be a template ref or an array of them; the observer is recreated when targets or `rootMargin` change, and it disconnects automatically when the owning scope is disposed. A lazy-loading panel typically calls `stop()` inside the callback once the entry is intersecting, because it only needs to know once. ## Conventions shared across VueUse - **Side effects clean themselves up** when the component or effect scope that created them is disposed; some functions also return a stop handle. - **Arguments accept refs or getters** in most places, so inputs stay reactive. - **Globals are configurable**: many functions take a `window` or `document` option for iframes, tests and server rendering. - **Feature detection is exposed** as `isSupported` where a browser API is not universal. ## When not to reach for it VueUse is not a reason to add a dependency for a single line you already own and test. It pays off when a behaviour has lifecycle, reactivity or browser-support edges — listeners, observers, storage, timers, debouncing — which is exactly where hand-written code tends to leak. Two habits keep it predictable: - **Destructure the returned refs** — `const { width, height } = useWindowSize()` — or wrap the result in `reactive()` if you prefer property access without `.value`; both are documented patterns. - **Pin the major version and read its release notes.** VueUse majors change defaults and remove helpers: version 15 removed `templateRef` in favour of Vue's `useTemplateRef` and changed `useThrottleFn`'s trailing default, so an unreviewed upgrade can alter behaviour without a type error.

  • How does useEventListener behave when its target template ref sits on an element toggled by v-if?
    It watches the target. While the ref is `null` nothing is attached; when the element appears the listener is added, and when the element is replaced the old listener is removed and a new one registered. You never re-attach by hand.
  • What should replace VueUse's templateRef helper in a VueUse 15 codebase?
    Vue 3.5's own `useTemplateRef('name')`. VueUse 15 removed `templateRef` because Vue now provides the same thing natively, and VueUse composables accept the ref it returns as a target.

saying these in an interview costs you the question

  • VueUse composables need an onUnmounted call to remove their listeners.
  • useEventListener attaches to document when no target is given.
  • A null template ref passed to useEventListener throws during setup.
  • useIntersectionObserver keeps observing after the component unmounts.
  • VueUse is a component library you register with app.use().