Jankapunkt
Jankapunkt

Reputation: 8423

Vue 3 get delta object of reactive updates

Consider a reactive object (or array) that is dynamically populated with data:

import { reactive, computed } from 'vue'

const entries = reactive({}) // global state beyond Template level
const docs = [
  { id: 1, name: 'foo', value: 'bar' },
  { id: 2, name: 'bar', value: 'moo' }
]

Object.entries(docs).forEach(doc => {
  entries[doc.id] = doc
})


export const useEntries = () => {
  const getAll = computed(() => Object.values(entries))
  const update = (id, newValue) => {
    entries[id].value = newValue
  }
  const changes = () => { /* ??? */ }

  return { getAll, update, changes } 
}

For performance reason, I want a Templates to watch only updated entries and not iterate the entire list, if the update is triggered within another Template.

Template A:

<script setup>
import { useEntries } from '/path/to/useEntries'

const { update } = useEntries()
</script>
<template>
  <button @click="update(1, 'moo)">Make moo</button>
</template>

Template B:

<script setup>
import { useEntries } from '/path/to/useEntries'
import { watch } from 'vue'

const { changes } = useEntries()

watch(changes, (entries) => {
  // process only updated entries here
})
</script>

Note, in this example I update only one entry, but it also might be 50 at once in a list of hundreds. I'd like to avoid a second list to manage.

Upvotes: 0

Views: 37

Answers (1)

Hasan Raza
Hasan Raza

Reputation: 525

you can manage "delta" object that holds changes you store entry’s id in the changes object then add changes function that returns only entries that have been updated

import { reactive, computed } from 'vue'

const entries = reactive({})  
const updatedEntries = reactive(new Set()) 

const docs = [
  { id: 1, name: 'foo', value: 'bar' },
  { id: 2, name: 'bar', value: 'moo' }
]

docs.forEach(doc => {
  entries[doc.id] = doc
})

export const useEntries = () => {
  const getAll = computed(() => Object.values(entries))

  const update = (id, newValue) => {
    entries[id].value = newValue
    updatedEntries.add(id) 
  }

  const changes = computed(() => {
    const changed = {}
    updatedEntries.forEach(id => {
      changed[id] = entries[id]
    })
    return changed
  })

  return { getAll, update, changes }
}

Template A

<template>
  <button @click="update(1, 'moo')">Make moo</button>
</template>

<script setup>
import { useEntries } from '/path/to/useEntries'

const { update } = useEntries()
</script>

Template B

<template>
  <div v-for="entry in changedEntries" :key="entry.id">
    {{ entry.name }}: {{ entry.value }}
  </div>

</template>

<script setup>
import { useEntries } from '/path/to/useEntries'
import { watch, ref } from 'vue'

const { changes } = useEntries()
const changedEntries = ref({})

watch(changes, (newChanges) => {
  changedEntries.value = newChanges
})
</script>

Upvotes: 2

Related Questions