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

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);
});

Related

mobx computed always update that computed doesn't work

import {makeAutoObservable} from "mobx";
class Test {
id = 0
constructor() {
makeAutoObservable(this)
}
get total() {
console.log('enss')
return 2;
}
}
const store = new Test();
export default store;
call:
import {isComputed, isComputedProp} from "mobx";
console.log(isComputedProp(Test, 'total'),Test.total,Test.total, Test.total, isComputedProp(Test, 'total'))
console output:
enss
enss
enss
true 2 2 2 true
the computed did not work and does not serve as a cache.
i using mobx 6.6 version in react 18.
Thank you very much for your answer!
This is explained in the docs here
It sometimes confuses people new to MobX, perhaps used to a library like Reselect, that if you create a computed property but don't use it anywhere in a reaction, it is not memoized and appears to be recomputed more often than necessary. For example, if we extended the above example with calling console.log(order.total) twice, after we called stop(), the value would be recomputed twice.
Basically it won't be cached if you use it outside of reactive context.
You can use computed({ keepAlive: true }) option to change this behaviour

Mobx : changing (observed) observable values without using an action is not allowed (asynchronus handler)

we know all this problem, and know how to solve it.
But I have an stranger Error in this context where I could use a good hint:
this.getData(id)
.then((response) => {
this.root.basic.selected.num = response.data.number; // number
this.root.basic.selected.date = response.data.date; // Date
this.root.basic.selected.text = response.data.text; // String. <- There is the error
}
})
The selected Object is bound by the "makeAutoObserveable()" Nothing special here. But a lot of code to push it all here.
Why this error happens only on the .text setter not on the others?
Is this a possible known behavior?
I solved it with "runInAction()" but I would like to understand the reason.
Kind Regards
Gregor

Why does vue array become observer?

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);
}
}
},

Stencil: Sudden build error without message after adding `#Method` to component

I cannot give too much information here (because there really isn't), but only this:
All of the sudden, after adding a #Method function to a stencil component:
#Method()
async setMenuItems(items: Element[]): Promise<void> {
// code here
}
the component stopped compiling with the following - really unhelpful - error:
[ ERROR ] ./src/components/menu-content/menu-content.tsx:63:44
build error
L62: #Method()
L63: async setMenuItems(elements: Element[]): Promise<void> {
L64: const unsupportedChildren = elements.filter(e => !this.isSupportedChild(e)).map(e => e.tagName);
[12:37.1] build failed in 7.02 s
Things to notice
the return type Promise<void> inside the error-message is highlighted red
there are other #Methods that do work within this component (even with the same return type).
the "broken" #Method is structurally equal to those that do work.
TypeScript compiler does not complain about anything
Only stencil compiler fails
I already tried...
to google for this issue - did not find any hints to this problem
to remove the async and add return Promise.resolve()
to rename the method (I mean.. why not)
to move the method to another place in class
to remove the whole method (compiles fine x( )
to remove the #Method decorator (compiled, but of course my method is removed from API)
to delete node_modules folder and reinstall
I remember that I already had this error once, and apparently I somehow fixed it (or not, idk). But if I did, I cannot remember how.
Does anyone have an idea how to debug this - or even better fix?
I figured it out. A more complete version of my component is:
import { Element, ... } from '#stencil/core';
class MenuContent {
#Element() element: MenuContentElement;
#Method()
setMenuItems(elements: Element[]): Promise<void> {
// ------------------^^^^^^^
// Element is meant to be the built-in browser interface for Element
// but stencil thinks that this is the imported Element from '#stencil/core'!
}
}
The exact problem here is, that the stencil-compiler seems to assume that the elements parameter is of type Element that is imported from #stencil/core which is obviously wrong and leads to this strange unhelpful error.
Possible Solutions
1. Use an alias type for the built-in Element type
// types.ts
export type DomElement = Element;
// menu-content.tsx
import { DomElement } from './types';
...
async setMenuItems(elements: DomElement[]): Promise<void> { ... }
2. Switch to HTMLElement
Note: This is only legit, when you don't need to support other Element-types such as SVGElements for example!
async setMenuItems(elements: HTMLElement[]): Promise<void> { ... }
3. Use alias in import statement
Please note: When using #stencil eslint rules, they will complain about your renamed import and say that "own class members are not allowed to be public".
I created a ticket for it here: https://github.com/ionic-team/stencil-eslint/issues/28
import { Element as ElementDecorator} from '#stencil/core';
class MenuContent {
// eslint will complain about that:
// "Own class properties cannot be public."
#ElementDecorator() element: HTMLMenuContentElement;
}
I experienced this same issue not with the Element type but with the Event type, so it appears to be rooted deeper - also about a year after you reported this issue it seems to still be a problem with Stencil

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!