Vue update computed property only when outcome changes - vue.js

Consider the following simplification of a problem I have:
export default {
data () {
return {
i_change_alot: 0,
};
},
mounted() {
setInterval(() => {
this.i_change_alot = Math.random();
}, 10);
},
computed: {
someComputedValue() {
this.i_change_alot;
return 'a';
}
}
}
I created a property i_change_alot which is changed every 10 milliseconds to a random value. This means the property becomes extremely reactive and thus it will trigger the computed property.
By simply calling this.i_change_alot I trigger a dependency (for the purposes of example), however the outcome of the computed property never changes.
The end result is that the computed property someComputedValue updates every 10 ms and this triggers a view rerender as far as I am aware.
How can I make it so that someComputedValue is only rerendered when the value/outcome changes?
(The original problem is about showing reactive relative dates, e.g. "1 second ago", "2 seconds ago" and so on. However, after some time, this becomes 30 minutes ago, 31 minutes ago which means for a full minute the string representation has not changed, but it is still rerendered every 10 ms because of the dependent date property).
According to https://github.com/vuejs/vue/issues/11399 I could create a structure with a watcher but it looks so counterintuitive.

Why does Vue trigger the computed when the value is unchanged?
Because Vue will never know if the final result is changed or not until it makes the re-calculation. So the computed variable will be re-calculated every time its dependencies changed and there is no way to avoid it.
The common misunderstanding is Vue cache the computed variables by their value but actually, Vue cache the computed variables by their dependencies' state.
To avoid re-render too frequently
You can create a watcher (as you already know) or wrap your template that uses the computed value into a component.
Why wrap into another component help here?
Because Vue will convert your template to a render function. This function will be re-calculated every time its dependencies change. Sound familiar? Right, it works like a computed variable. The dependencies of a render function are all the variables that you use in the template. So, if you wrap your frequent change variable into a component, Vue will re-render that component only and avoid re-rendering your big component. It will have a big impact on your performance

Related

What causes Vue 2 to check a "get" function/property?

My recent work in Vue (we're still using Vue 2 unfortunately) has caused me to question my understanding of how Vue checks property values and re-renders.
I've got a couple of components on my page which have a v-show clause tied to a get statement in the code:
<my-component v-show="this.isRequired">
public get isRequired(): boolean {
if(this.model.myBooleanProperty == true && this.model.myNumberProperty > 0) {
return true;
}
if(this.model.myOtherBooleanProperty == true && this.model.myOtherNumberProperty > 0) {
return true;
}
return false;
}
Now, my understanding is that Vue would check this function whenever any of the involved properties changed. So if the values of any out of myBooleanProperty, myNumberProperty, myOtherBooleanProperty and myOtherNumberProperty changed then isRequired would be checked and the v-show clause would cause the component to show or not show depending on the outcome.
However, I've learned this isn't the case. By commenting out parts of the function, it seems that only changes to myBooleanProperty, myNumberProperty, myOtherBooleanProperty ever cause isRequired to be checked, even if they're taken out of the function. myOtherNumberProperty never causes it to be checked, even if it's directly manipulated in isRequired by setting it to zero or null.
Can someone please explain what, under these circumstances, causes Vue to reevaluate the value of isRequired?
Don't use this in the template, it's not necessary.
I understand you use vue-property-decorator library, right? Cause a get x property is a class getter, which is compiled down to a Vue computed property.
You are right, computed properties react and reevaluate whenever one of their reactive dependency updates. myOtherNumberProperty should also trigger the computed to reevaluate its value.
Maybe you have a reactivity problem with this property. Check that this value is properly initialized in your data function. If it's missing in the initial model object, Vue won't make it reactive and thus, its changes won't trigger anything.
I can't help further more without additional context on your code.

VueJS2, how to call props in data

I created a component, I want to call the model value. If I do it the first way, but when I try to do it the second, I get this error.
[Vue warn]: Error in data(): "TypeError: Cannot read properties of undefined (reading 'value')"
The first way
The second way
I want to use second way because i want create own component vuetify date-picker and i want to pass date to him.
Unless it is possible to rewrite the first one in such a way that I can write the date in this way. Is there any way to "vm"? I am using this https://vuetifyjs.com/en/components/date-pickers/#formatting
Thank you for answering
Vue automatically binds the regular functions that are provided to its data or methods sections to the component instance. However, arrow functions can not be bound - the this keyword inside arrow functions refers to the closest outer context/scope. But since there is no such context for the data section - your this is undefined.
You should either use a regular function instead of arrow function when defining your data section - or you should leave the values in the arrow function undefined and assign the initial values inside the created hook.
You should use in arrow function like this.
props: ['value'],
data: vm => ({
props: vm.value
});

Retrieve an atom/selector by key (string)?

When using Recoil.js, one creates an atom by handing atom() an object that include a key (a string):
const textState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: '', // default value (aka initial value)
});
Later on one can get the value (and a setter) by handing the thing that atom() returns to something like useRecoilState:
function TextInput() {
const [text, setText] = useRecoilState(textState);
It's fine that I need to first create the atom using atom() but after that I'd love to get the value (and the setter) using the string key. I'm imagining something like this:
function TextInput() {
const [text, setText] = useRecoilState('textState');
The use case for this is that I could then create all my atoms (and selectors) in places that make sense (i.e., higher up the hierarchy) and then have components access that state without having to include the atoms from the file that originally created them.
Is it possible to get the value/setter function for Recoil atoms/selectors using the key (the string/text) instead of having to hand useRecoilState() (etc) the thing returned from atom()?
No it's not possible. The value that the call to atom() returns is a reference to the state, that the useRecoil... hooks need to access it. This also wouldn't work with atomFamilies which need a parameter to access a specific atom.
I am also not sure what the benefit would be. You can still create the atoms somewhere up in the hierarchy if you want to. I am also not sure what you mean by "without having to include the atoms from the file that originally created them". What would be the problem with that?
The whole idea of Recoil is to have a state tree orthogonal to your component tree, so there is no need for creation higher up in the hierarchy. Atoms are created where they are needed during runtime.
It feels like you want to have more of a redux like pattern with atoms being created in one place up in the component tree, which defies this core idea of recoil that is setting it apart from the more flux like state management patterns.

Why next function in beforeRouteEnter happends after the component is loaded?

In beforeRouteEnter event I call to promise/http which take a second to get some data.
After I get the data I pass to the result prop in the component. (using next function).
Also I have a getter that take the value from the prop.
The problem is vue is invoke the getter before it set the value (by next function), so I got undefined.
How can I solve this error?
Here is an code example of the problem
Problem is that when you use next(vm ...) you work with context of page component where is your #Prop defined. You can't modify #Prop value this way. There is some hacks how you can do that but is it really necessary to use #Prop in this case? Why you simply don't use basic variable in page component?
If you replace
#Prop() result
with:
result: Object = {};
everything works great.
If you really need prop here maybe you can just copy prop value to some variable (use watch if you need detect changes) but i don't see any purpose for prop here.
Hope this answer helps you :)

VueJS: Why Trigger 'Input' Event Within 'Input' Event Handler?

I'm learning VueJS. I'm figuring out their currency validation example code.
Vue.component('currency-input', {
template: `
<span>
$
<input
ref="input"
v-bind:value="value"
v-on:input="updateValue($event.target.value)">
</span>
`,
props: ['value'],
methods: {
// Instead of updating the value directly, this
// method is used to format and place constraints
// on the input's value
updateValue: function (value) {
var formattedValue = value
// Remove whitespace on either side
.trim()
// Shorten to 2 decimal places
.slice(
0,
value.indexOf('.') === -1
? value.length
: value.indexOf('.') + 3
)
// If the value was not already normalized,
// manually override it to conform
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
// Emit the number value through the input event
this.$emit('input', Number(formattedValue))
}
}
})
The $emit call at the bottom of the updateValue function, triggers an input event.
When I comment it out, the real time currency validation no longer works. So I realize it has a purpose.
But why trigger an input event inside an input event?
You'd think the input event would fire again, causing the updateValue handler to fire again, causing a stack overflow due to recursive calls.
I understand VueJS's much simpler $emit example code. It's just like Jquery's trigger function.
vm.$on('test', function (msg) {
console.log(msg)
})
vm.$emit('test', 'hi')
// -> "hi"
But in the currency validation example, I do not understand why $emit is used the way it's used, and why it works the way it works.
Can somebody explain?
The Emit call here is to allow you to hook into the event in parent contexts. The Input event is also used by the v-model directive to handle two way binding with components.
v-model='model' is essentially v-bind:value='model' v-on:input='model = $event.target.value' with some added bits to make it play nice. When you remove the this.$emit('input', Number(formattedValue)) You're removing the mechanism that updates the value outside the component.
EDIT: #Jay careful what you wish for sometimes
All elements in HTML have a series of native handlers for the common events; resize, load, unload, etc. These handle what to do when the page changes it's rendering and can be disabled or added onto, since the introduction of JavaScript browsers have used an event pump system that allows multiple functions to be attached to any event which run in sequence when the event is raised. An example being how you can have 3 functions run on resize to handle edge cases such as minimum/maximum size, screen orientation etc.
Form elements generally implement their own base event functions: keydown, keyup, mousedown, mouseup. These base functions invoke events to make our lives easier as developers, these being: input, blur, focus. Some have specialized events as in select elements implementing change, form tags implementing submit.
Input tags on focus capture keyboard input and display the text input cursor to indicate that it's ready to receive input. It adds in handlers for the tab keycode which finds the next available input and shifts focus to that element. The event pump style function system is great here as it allows you to bind to focus and do things like change the background color or border when the input is focused without having to implement the code for capturing input or displaying the cursor yourself.
Input tags also raise the input event when you type in them indicating that the input has changed, telling the browser to change the value and update the display so that the functionality expected by the user is consistent.
In the currency-input example we are adding the updateValue function to work with the native function and process the input value of the event, in the updateValue function we modify the string representation of the value and need someplace to put it. You could simply add a data property to hold the value and bind the input's value property to the data property allowing the currency-input to internally handle the display of the result but that would lock the value behind a private accessor and you would be unable to modify or retrieve the value of the resulting currency formatted value.
Using this.$emit('input', Number(formattedValue)) the updateValue function is acting similar to the native input tag by raising an event that can be captured by the parent context and worked with. You can store it in a value, use it as the basis for a function, or even ignore it completely though that may not help much. This allows you to keep track of the value of the input and modify it as needed or send it to the server, display it, etc.
It also ties into a few directives most pertinently v-model which is syntactic sugar to allow for a value property binding and an input event binding to a data property inside the current context. By providing a value prop and emitting an input event a custom element can act similar to a native form element in the systems of a Vue application. An extremely attractive feature when you want to package and distribute or reuse components.
It's a lot nicer to go:
...
<currency-input v-model='dollarValue'></currency-input>
<input v-model='dollarValue'>
...
Than to have to add in value and input bindings everywhere ergo:
...
<currency-input v-bind:value='dollarValue' v-on:input='updateDollarValue($event.target.value)'></currency-input>
<input v-bind:value='dollarValue' v-on:input='updateDollarValue($event.target.value)'>
...
Now that my weird rambling is done, I hope this helped with understanding some of the patterns and reasoning behind the currency-input example.