npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tracked-instance

v2.0.9

Published

Build large forms and track all changes

Readme

tracked-instance

Track form changes in Vue 3 and send only modified fields to the backend — no more diffing payloads by hand.

const {data, changedData, isDirty} = useTrackedInstance({name: 'Jack', age: 30})

data.value.name = 'John'

changedData.value  // { name: 'John' }   ← only what changed
isDirty.value      // true

data.value.name = 'Jack'  // revert
changedData.value  // undefined           ← back to clean
isDirty.value      // false

Install

npm i tracked-instance

Supports Vue 3 only.


useTrackedInstance  ·  ▶ Try on playground

Track changes to a single object, primitive, or array.

import {useTrackedInstance} from 'tracked-instance'

const {data, changedData, isDirty, loadData, reset} = useTrackedInstance({
  name: 'Jack',
  isActive: false,
})

Mutate data.value directlychangedData and isDirty update automatically:

data.value.name = 'John'
isDirty.value      // true
changedData.value  // { name: 'John' }

// Revert to original value → field disappears from changedData
data.value.name = 'Jack'
isDirty.value      // false
changedData.value  // undefined

reset() — revert all changes back to the last loaded baseline:

data.value.name = 'John'
reset()
data.value  // { name: 'Jack', isActive: false }

loadData(newData) — replace data without marking anything dirty (use after a successful save):

loadData({name: 'Joe', isActive: true})
isDirty.value  // false  ← Joe is now the new baseline

Works with primitives and arrays too:

useTrackedInstance(false)
useTrackedInstance([1, 2, 3])

Custom equality with equals

By default values are compared with ===. Override this for edge cases — for example when a UI component writes null but the backend sends "":

const {data, isDirty} = useTrackedInstance(
  {comment: null},
  {equals: (a, b) => (a ?? '') === (b ?? '')}
)

data.value.comment = ''     // treated as equal to null
isDirty.value               // false

data.value.comment = 'hi'
isDirty.value               // true

useCollection  ·  ▶ Try on playground

Track an array of items — add, remove, modify, and reset the whole list.

import {useCollection} from 'tracked-instance'

const {items, isDirty, add, remove, loadData, reset} = useCollection()

loadData([{name: 'Jack'}, {name: 'John'}, {name: 'Joe'}])

Each item in items is a CollectionItem with its own TrackedInstance:

items.value[0].instance.data.value.name = 'Stepan'
isDirty.value  // true

add(item, index?) — add a new item (marked isNew: true):

const newItem = add({name: 'Taras'})
// newItem.isNew.value === true
// newItem.isRemoved.value === false

add({name: 'Taras'}, 0)  // insert at position 0

remove(index, isHardRemove?) — soft-delete by default, hard-delete with true:

remove(0)        // soft remove: isRemoved = true, item stays in array
remove(0, true)  // hard remove: spliced out immediately

Soft-removed items can be restored with reset() or by setting isRemoved.value = false manually.

reset() — removes new items, restores soft-removed ones, reverts all changes:

reset()

Item meta

Attach computed or reactive metadata to each item via a factory function:

const {add, items} = useCollection(instance => ({
  isValidName: computed(() => instance.data.value.name.length > 0)
}))

add({name: ''})
items.value[0].meta.isValidName.value  // false

The same options (including equals) are forwarded to every TrackedInstance in the collection:

const {items} = useCollection(
  () => undefined,
  {equals: (a, b) => (a ?? '') === (b ?? '')}
)

API Reference

useTrackedInstance(initialData?, options?)

useTrackedInstance<Data>(initialData ? : Data, options ? : TrackedInstanceOptions)
:
TrackedInstance<Data>

| Option | Type | Description | |----------|---------------------------------------|------------------------------------------------------------| | equals | (a: unknown, b: unknown) => boolean | Custom equality for primitive leaf values. Replaces ===. |

| Return | Type | Description | |---------------------|--------------------------|-------------------------------------------------------------| | data | Ref<Data> | Reactive reference to current data. Mutate directly. | | changedData | ComputedRef<DeepPartial<Data>> | Only modified fields. undefined when nothing has changed. | | isDirty | ComputedRef<boolean> | true when any field differs from the original. | | loadData(newData) | void | Replace data and clear dirty state (new baseline). | | reset() | void | Revert all changes back to the last loadData() baseline. |

useCollection(options?)

useCollection<Item, Meta>(options?: CollectionOptions<Item, Meta>): Collection<Item, Meta>

| Option | Type | Description | |------------------|--------------------------------------------|------------------------------------------------------------| | equals | (a: unknown, b: unknown) => boolean | Custom equality for primitive leaf values. Replaces ===. | | createItemMeta | (instance: TrackedInstance<Item>) => Meta | Factory for per-item metadata, called on loadData/add. |

| Return | Type | Description | |--------------------------------|-------------------------|------------------------------------------------------------------| | items | Ref<CollectionItem[]> | Reactive array of collection items. | | isDirty | ComputedRef<boolean> | true if any item is dirty, new, or soft-removed. | | add(item, index?) | CollectionItem | Add a new item. Appended to end by default. | | remove(index, isHardRemove?) | void | Soft-remove by default. Pass true to splice from array. | | loadData(items) | void | Replace all items and clear dirty state. | | reset() | void | Remove new items, restore soft-removed, reset all instance data. |

CollectionItem

interface CollectionItem<Item, Meta = undefined> {
  instance: TrackedInstance<Item>                 // tracked instance for this item
  isNew: Ref<boolean>                             // true for items added via add()
  isRemoved: Ref<boolean>                         // true after soft remove
  meta: Meta                                      // custom metadata from createItemMeta()
  remove(isHardRemove?: boolean): void            // shortcut to remove self
}