UNPKG

tracked-instance

Version:
219 lines (156 loc) 11.5 kB
# tracked-instance <a href="https://www.npmjs.com/package/tracked-instance"><img src="https://img.shields.io/npm/v/tracked-instance.svg?sanitize=true" alt="Version"></a> > Track form changes in Vue 3 and send only modified fields to the backend — no more diffing payloads by hand. ```js 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 ```bash npm i tracked-instance ``` Supports **Vue 3** only. --- ## useTrackedInstance &nbsp;·&nbsp; [▶ Try on playground](https://play.vuejs.org/#eNqtVttuGzcQ/ZWpglYyoJuV1AnWstI29kPS1jFivxTQC3eXq2XEJQmSK1mQDeQPArQFigIF+tZvKFCgH+MfaD+hQ+5FK8WXFg1sCEvOkHPOcOaQ69aXSvUXOW0FrbGJNFMWDLW5Ak7E7GjasmbamkwFy5TUFtagaQLXkGiZQRuXtRum3NALTaI5jV8KY4mIaO1pi/keKw24bCrsSlH4Vi4YhSNYTwUAiwMQeRZS7UaWWU4DMFYzMXMTK0p05QBX+MG5X2XO8pAzk1JcHkrJKcHdAWKmaWQlLvGbAwiSbe13PRX4PxWRRFhIICaWdCFKkTmNj/2AmWOm7aoLXJJySlNMEDI7uoXw2NOZdGo2+90Gk3bbjyoanPvhFvyEcEP9dAN9Cbzdhms0Xe85zBWeu0JdpBSOiZ7D14LNUtuMPBoOn30Y2er8zsAvUs2MlSrFvJ9KrIwmkiJ7zJyTBeYV04Il0vE80FwYOTH2nAp7RlYOd+EzfkMjqeNxcR5dyMVcyKWYlCc76bjfRgRDFq5QiFmJCDp7cDQpi6aM3F8QnjsPx8QZdqLW9lfnr0/7imhDO/6zAMCSVadx9IX3HsYHIEvCLAi6hDOsZobrtIuOZXDBMipz29FdeDYcFt71ybhyKre5FafPkS/B8aBoPWw0HFiaKU4sxRHAOGYLLNkVp9iMMTNoWQUJp5eHMCMqGD1R+JVIYXsJyRhfBYYI0zNUs+QQFIljjBjsH6hL38ZFH2zt6fYK9r3V2dCaPp6cxEg4c7U8HuCwWohGTkLKJxeuysahrhehhQmVW1j0MhlT7sA6+r4cpy1wve7UhF5aHFWxlyy2abA/HH6Kk4MNgkERxQUoguyE/w7r+L7o/UIjKhCu7DcYSoH5/yhQGXyj/Js8VE3Vdx310fPxUPRGo29iRymN5qHEwmiEAth4Phw9zK2VAr6IOIvmuKWXRtwvwEIlIacxzn1SSihW2M3Pf/z1+3t447zGg2JxHfkzERp1eOfWrvvv2BmururmatQxwHq9kaXn0C6+bt791gbUtL9//f5PwClaCr5BTcNWrDhvoRsPsGP+Q/tsIJxbbOQdbuhjFBEQVHusIZLcCW5FCME+ogdP6WjkoT4aPSX0YIgAt+iVBOslNz+9RxF1iYo3nHD5zS8/ePGMmwwdKYeiUWTbjR5OGmqI+ZjAzbsfYZkSCzNqDaqfsGAlWLxoUG4WVAf1WqVxriQX4gU50zIXcfAo+dz9NXRp5NQrxFuA6p4mMctNgFJ1CBkTvZS6iys4GHrtQqoNPPAc+XaEtKk729Kw5/iNBxi8yaNSU+wJliCenVthK6NI+hs0F9xU4RA47g2Xu7hRkpBk/0FuBZXdC3GDuzqMrUtguwKr7y2nVrdVPMR6GVH9t0YKfNH5+3FaGvAhV7+EUHl2XmTOOG2l1ioTDAa5UPNZP5LZ4EO/6uWEEa3Bqzlhs514uE4xTvVrZRle3VtxCedy+crP1S8Ov8ap0S3zbw2mzEE7c+KCZTZt1TZLNJZiYT45P/VaWhtRAHN39dxjRCGSPHcYC7ev8CARdsPPo33p04dnemFOLi0VpiJVPTTcW8h543v4xT3UN3Af95/UWbz+B3cQyP8=) Track changes to a single object, primitive, or array. ```js import {useTrackedInstance} from 'tracked-instance' const {data, changedData, isDirty, loadData, reset} = useTrackedInstance({ name: 'Jack', isActive: false, }) ``` **Mutate `data.value` directly** — `changedData` and `isDirty` update automatically: ```js 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: ```js data.value.name = 'John' reset() data.value // { name: 'Jack', isActive: false } ``` **`loadData(newData)`** — replace data without marking anything dirty (use after a successful save): ```js loadData({name: 'Joe', isActive: true}) isDirty.value // false ← Joe is now the new baseline ``` Works with primitives and arrays too: ```js 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 `""`: ```js 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 &nbsp;·&nbsp; [▶ Try on playground](https://play.vuejs.org/#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) Track an array of items — add, remove, modify, and reset the whole list. ```js 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`: ```js items.value[0].instance.data.value.name = 'Stepan' isDirty.value // true ``` **`add(item, index?)`** — add a new item (marked `isNew: true`): ```js 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`: ```js 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: ```js reset() ``` ### Item meta Attach computed or reactive metadata to each item via a factory function: ```js 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: ```js const {items} = useCollection( () => undefined, {equals: (a, b) => (a ?? '') === (b ?? '')} ) ``` --- ## API Reference ### useTrackedInstance(initialData?, options?) ```typescript 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?) ```typescript 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 ```typescript 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 } ```