Why does vue array become observer? - vuejs2

I have a variable defined in data
data() {
return {
pie_graph_data:[],
}
}
and this variable is not being anywhere. After getting response from server
let response = resp.data.success.pie_graph_data;
console.log(response);//this is array
console.log(this.pie_graph_data);//this is also an array
this.pie_graph_data = response;
console.log(response, this.pie_graph_data); //both become observer
I need to know if this.pie_graph_data is not used anywhere why its turning to observer
and more importantly How do I use this variable in template as array.

Logging pie_graph_data made you believe that it is just a plain empty array but actually it is not. If you can not see your arrays properties in your browsers console, you can log your empty arrays pie_graph_data.__ob__ property and you will see it is already an Observer. Arrays are iterable object and assigning additional properties doesn't make them lose their ability to be iterated over them. So there must be an other reason why chartjs is not working for you.

The issue was in child component where computed property was using the observer being passed by props.
Converting the computed property into method(getChartData) and calling it in watcher solved the issue for me.
watch:{
graphData(val){
if(val && val.length){
this.renderChart(this.getChartData(val),this.chartOptions);
}
}
},

Related

trace() error "can only be used inside a tracked computed value or a Reaction" when used inside computed

I've used MobX for a few years now, and love it, but sometimes my trace calls are not functioning, and I don't understand why not. There must be some fundamental thing that I've completely misunderstood, but most likely have been lucky enough to get through anyway. Here's an example of using trace() where I'm getting an error:
import { computed, observable, trace } from "mobx";
class Stat {
#observable baseValue = 1;
#computed get value() {
trace();
return this.baseValue;
}
}
const strength = new Stat();
strength.baseValue = strength.baseValue + 1;
The expected output, in my mind, is that trace reacts to the change in "baseValue" and logs the change. Instead, I'm getting the following error:
Error: [MobX] 'trace(break?)' can only be used inside a tracked computed value or a Reaction. Consider passing in the computed value or reaction explicitly
"Inside a tracked computed value" is, to my understanding, exactly what I'm doing. Or..?
Full sandbox: https://codesandbox.io/s/mobx-trace-trouble-ki2qj?file=/index.ts:0-312
As far as I understand this phrase
inside a tracked computed value or a Reaction.
you need to access computed value inside reactive context, like inside observer or reaction or autorun. Otherwise trace just don't have information about what is going on because your computed value is untracked at that moment by any observer.
So this will work:
const MyComponent = observer(() => {
return <div>{strength.value}</name>
})
or this
autorun(() => {
console.log(strength.value);
});

Vue effectively using computed property

I have the below code in my computed property. The function is expected to get way complicated. Is it correct to have it all here? I would like to have it in my store file, but I'm not able to call a function by name from within the computed property. Any advice ?
computed: {
assignValue() {
this.valueToSet = this.value1;
if (this.valueToSet < 10) {
return "1 week"
} else if (this.valueToSet < 20) {
return "2 weeks"
} else if (this.valueToSet < 30) {
return "3 weeks"
} else {
return 0;
}
}
}
To summarize, I would like to have it in my store.js (vuex), but how can I call/trigger a function by name inside "the computer property".
If it is not possible, any effective alternative? Or I should continue this way?
Vuex itself has getter properties that you can use with Vue component's computed property as shown in the docs.
As far as your code is concerned, there is no problem with your current approach. You should move this code to Vuex getter when you need to re-use that state in multiple components.
The only problem in your code is this.valueToSet = this.value1;. Computed properties should not produce a side-effect (including assignment). There is no direct harm, but it can have unintended consequences internally as computed values are cached. If you still need this part - this.valueToSet = this.value1; - move it to other computed property or use watch expression.

Pass parameter while emitting using Vuejs event hub

I have checked similar question, but there is still one thing that is unclear to me:
Can I pass a parameter in emit on event hub, but I need parameter to be VALUE and not the VARIABLE which stores value. So for example: eventHub.$emit('test_emit', true) and the method which is called on test_emit should have it's parameter set on true.
From the similar question that you provided, you would just replace name with true when you are emitting event
methods: {
showModal(name) { this.bus.$emit('showModal', true); },
}
created() {
// `show` will have the value that you emitted
this.bus.$on('showModal', (show) => console.log(show);
}
Sure you can, what you cannot do is to pass more then one variable (like eventHub.$emit('test_emit', true, false) as $emit accepts only one additional parameter (that can be the value or an object containing the key: value associations, also know as payload.

Passing custom parameters in render function

I have below code to create column:
DTColumnBuilder.newColumn(null).withTitle('Validation').renderWith(validationRenderer)
and render function:
function validationRenderer(data, type, full, meta) {
.......
}
Now, I want to pass custom parameters to validationRenderer so that I can access it inside the function, like below:
DTColumnBuilder.newColumn(null).withTitle('Validation').renderWith(validationRenderer('abc'))
function validationRenderer(data, type, full, meta, additionalParam) {
// do something with additionalParam
}
I could not find it in the documentation but there must be something to pass additional parameters in meta as per the reference from here
Yes, you can. Or, better, you technically can, but you may use a clever workaround to handle your issue.
I had this issue today, and found a pretty sad (but working) solution.
Basically, the big problem is that the render function is a parameter passed to the datatable handler, which is (of course) isolated.
In my case, to make a pratical example, I had to add several dynamic buttons, each with a different action, to a dynamic datatable.
Apparently, there was no solution, until I thought the following: the problem seems to be that the renderer function scope is somewhat isolated and unaccessible. However, since the "return" of the function is called only when the datatable effectively renders the field, you may wrap the render function in a custom self-invoking-anonymous-function, providing arguments there to use them once the cell is being rendered.
Here is what I did with my practical example, considering the following points:
The goal was to pass the ID field of each row to several different custom functions, so the problem was passing the ID of the button to call when the button is effectively clicked (since you can't get any external reference of it when it is rendered).
I'm using a custom class, which is the following:
hxDatatableDynamicButton = function(label, onClick, classNames) {
this.label = label;
this.onClick = onClick;
this.classNames = this.classNames || 'col5p text-center';
}
Basically, it just creates an instance that I'm later using.
In this case, consider having an array of 2 different instances of these, one having a "test" label, and the other one having a "test2" label.
I'm injecting these instances through a for loop, hence I need to pass the "i" to my datatable to know which of the buttons is being pressed.
Since the code is actually quite big (the codebase is huge), here is the relevant snippet that you need to accomplish the trick:
scope.datatableAdditionalActionButtons.reverse();
scope._abstractDynamicClick = function(id, localReferenceID) {
scope.datatableAdditionalActionButtons[localReferenceID].onClick.call(null, id);
};
for (var i = 0; i < scope.datatableAdditionalActionButtons.length; i++) {
var _localReference = scope.datatableAdditionalActionButtons[i];
var hax = (function(i){
var _tmp = function (data, type, full, meta) {
var _label = scope.datatableAdditionalActionButtons[i].label;
return '<button class="btn btn-default" ng-click="_abstractDynamicClick('+full.id+', '+i+')">'+_label+'</button>';
}
return _tmp;
})(i);
dtColumns.unshift(DTColumnBuilder.newColumn(null).notSortable().renderWith(hax).withClass(_localReference.classNames));
}
So, where is the trick? the trick is entirely in the hax function, and here is why it works: instead of passing the regular renderWith function prototype, we are using a "custom" render, which has the same arguments (hence same parameters) as the default one. However, it is isolated in a self invoking anonymous function, which allows us to arbitrarely inject a parameter inside it and, so, allows us to distinguish, when rendering, which "i" it effectively is, since the isolated scope of the function is never lost in this case.
Basically, the output is as follow:
And the inspection actually shows that elements are effectively rendered differently, hence each "i" is being rendered properly, while it wouldn't have if the function wouldn't have been wrapped in a self invoking anonymous function:
So, basically, in your case, you would do something like this:
var _myValidator = (function(myAbcParam){
var _validate = function (data, type, full, meta) {
console.log("additional param is: ", myAbcParam); // logs "abc"
return '<button id="'+myAbcParam+'">Hello!</button>'; // <-- renders id ="abc"
}
return _validate ;
})('abc');
DTColumnBuilder.newColumn(null).withTitle('Validation').renderWith(_myValidator);
// <-- note that _myValidator is passed instead of "_myValidator()", since it is already executed and already returns a function.
I know this is not exactly the answer someone may be expecting, but if you need to accomplish something that complex in datatable it really looks like the only possible way to do this is using a self invoking anonymous function.
Hope this helps someone who is still having issues with this.

Knockout components using OOP and inheritance

I was hoping I could get some input on how to use Knockout components in an object-oriented fashion using Object.create (or equivalent). I'm also using Postbox and Lodash, in case some of my code seems confusing. I've currently built a bunch of components and would like to refactor them to reduce code redundancy. My components, so far, are just UI elements. I have custom input boxes and such. My initial approach was as follows, with some discretion taken to simplify the code and not get me fired :)
// Component.js
function Component() {
var self = this
self.value = ko.observable()
self.initial = ko.observable()
...
self.value.subscribeTo('revert', function() {
console.log('value reverted')
self.value(self.initial())
}
}
module.exports = Component
// InputBox.js
var Component = require('./Component')
var _ = require('lodash')
function InputBox(params) {
var self = this
_.merge(self, params) // quick way to attach passed in params to 'self'
...
}
InputBox.prototype = Object.create(new Component)
ko.components.register('input-box', InputBox)
Now this kind of works, but the issue I'm having is that when I use the InputBox in my HTML, I pass in the current value as a parameter (and it's also an observable because the value is retrieved from the server and passed down through several parent components before getting to the InputBox component). Then Lodash merges the params object with self, which already has a value observable, so that gets overwritten, as expected. The interesting part for me is that when I use postbox to broadcast the 'revert' event, the console.log fires, so the event subscription is still there, but the value doesn't revert. When I do this in the revert callback, console.log(self.value(), self.initial()), I get undefined. So somehow, passing in the value observable as a parameter to the InputBox viewmodel causes something to go haywire. When the page initially loads, the input box has the value retrieved from the server, so the value observable isn't completely broken, but changing the input field and then hitting cancel to revert it doesn't revert it.
I don't know if this makes much sense, but if it does and someone can help, I'd really appreciate it! And if I can provide more information, please let me know. Thanks!
JavaScript does not do classical inheritance like C++ and such. Prototypes are not superclasses. In particular, properties of prototypes are more like static class properties than instance properties: they are shared by all instances. It is usual in JS to have prototypes that only contain methods.
There are some libraries that overlay a classical-inheritance structure onto JavaScript. They usually use "extends" to create subclasses. I don't use them, so I can't recommmend any in particular, but you might look at Coffeescript if you like the classical-inheritance pattern.
I often hear "favor composition over inheritance," but I generally see a lot of emphasis on inheritance. As an alternative, consider Douglas Crockford's "class-free object-oriented programming", which does away with inheritance entirely.
For what you're trying to do here, you probably want to have InputBox initialize itself with Component, something like:
function InputBox(params) {
var self = this
Component.bind(self)(); // super()
_.merge(self, params) // quick way to attach passed in params to 'self'
...
}
The new, merged, value will not have the subscription from Component, because the subscription is particular to Component's instance of the observable, which will have been overwritten.
To everyone who responded, thank you very much! I've found a solution that works better for me and will share it here in case anyone is interested.
// Component.js (only relevant parts shown)
function Component(params) {
var self = this
_.merge(self, params)
self.value.subscribeTo('some event', function() {
// do some processing
return <new value for self.value>
}
module.exports = Component
// InputBox.js
var Component = require('./component')
function InputBox(params) {
var self = this
Component.call(self, params)
}
By taking this approach, I avoid the headache of using prototypes and worrying about the prototype chain since everything Component does is done directly to the "inheriting" class. Hope this helps someone else!