moment / luxon

⏱ A library for working with dates and times in JS
https://moment.github.io/luxon
MIT License
15.05k stars 730 forks source link

Durations can't be used on properties in Vue because of internal mutability #1630

Open hallipr opened 1 month ago

hallipr commented 1 month ago

Describe the bug Because Duration.toFormat mutates the Duration, this causes recursive updates in Vue for components that run toFormat on Duration properties getters.

To Reproduce with:

"dependencies": {
    "luxon": "^3.4.4",
    "vue": "^3.4.21"
  }

when I:

<template>
    <div>{{entry.duration.toFormat("hh:mm:ss")}}</div>
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import { Duration } from 'luxon'

class Entry {
  public get duration() { return Duration.fromMillis(20000) }
}

const entry = reactive(new Entry())
</script

I get:

<div>00:00:20</div>

But I also get

[Vue warn]: Unhandled error during execution of app errorHandler
Uncaught (in promise) Maximum recursive updates exceeded in component <App>. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.

Actual vs Expected behavior I would expect toFormat to not mutate the duration, but it does and this is detected by Vue.

Desktop (please complete the following information):

Additional context

This is another effect of the cause from https://github.com/moment/luxon/issues/1104

I originally posted this as a StackOverflow question https://stackoverflow.com/questions/78474017

icambron commented 1 month ago

Internal mutation is ok and is important for performance; these mutations in Luxon are just memoization. I think the issue is that we're not hiding it from Vue; i.e. Luxon's objects are immutable as far as the API is concerned, but Vue is seeing that notionally private members on the Duration object are changing, and it has no way to know they aren't part of an API. How does Vue detect mutations, and what's the supported way to hide things from it? This is supposed to be a tree-falls-in-the-woods sort of thing.

The gist here is that I'm willing to make changes that better support tools like Vue, but not at the expense of performance.

Nicolas-Yazzoom commented 1 month ago

You're likely looking for shallowReactive.