Description
Context
In React it's very common to have a controlled component. e.g. like this https://codesandbox.io/p/devbox/react-dev-forked-85xg4s where the state completely lives outside of the child component.
Although I don't think in the VueJS ecosystem uses the same wording, I have seen this pattern being used (e.g. https://stackoverflow.com/questions/68496743/vue-js-input-value-not-reflecting-value-in-component-data/79472786 which also demonstrates the issue I'm about to describe).
More indirect versions of this are where an event gets forwarded to a Vuex store, and the component (should) show the resulting state from the store.
Demonstration of bug
The simplest example that demonstrates this pattern is:
<div id="app">
<h2>controll text field can lead to inconsistent state</h2>
<input type="text" :value="name" @input="handleInput"/>
force update:
<input type="checkbox" v-model="forceUpdateWorkaround"/>
</div>
new Vue({
el: "#app",
data: {
name: 'foo',
forceUpdate: false
},
methods: {
handleInput: function (e) {
console.log("e.target.value =", e.target.value)
this.name = e.target.value.lower()
if (this.forceUpdate) {
this.$forceUpdate()
}
},
}
})
(full example here
When typing 'b' in the text field, it shows 'FOOB' in all caps, which mirrors the value
data. However, when we replace the last B' with a 'b', the text field will change to 'FOOb', but the event handler (
handleInput`) will not change the vue model, and therefore not update the component. Now, the internal data model and the DOM are inconsistent
Discussion
This is a rare and subtle (and therefore, I think dangerous) bug that can result from this pattern. ReactJS does not have this problem (see above example). What I think ReactJS does is that it will update the DOM with the vDOM for the DOM element that triggered the event. I think this makes a lot of sense since DOM element events can be the source of internal state changes (like the value of a text field), but those events do not always trigger re-renders in the frameworks (since state changes may not happen, as demonstrates above). In this edge case, a state change in a DOM element without an associated state change in the VueJS framework can cause inconsistencies.
Some background
We hit the same issues in a Python framework called Solara which is similar to React, and we were comparing how other frameworks solve this problem. We found that SolidJS and VueJS have this issue, while ReactJS does not. In the end, we use a solution similar to ReactJS in our underlying framework: widgetti/reacton#45
Proposed solution
VueJS should always update the DOM element that triggered an event if, after handling its event handlers, it did not trigger a rerender.