How to pass "read-only" props in SolidJS - solid-js

I frequently pass properties to Solid components that are read-only for dependency injection of singleton services. See the simplified example below. However the SolidJS lint rules give me errors like: The reactive variable 'props.firestore' should be used within JSX, a tracked scope (like createEffect), or inside an event handler function. It seems totally reasonable to have some readonly props like this. Am I misusing props in SolidJS or not understanding something? Or are the ESLint rules not sophisticated enough? I can disable the lint warnings but don't want to do that if I'm doing something wrong here.
export const DocumentView = (props: { firestore: Firestore }) => {
const query = query(collection(props.firestore), "my-collection");
const [docs, setDocs] = createSignal([]);
const unsubscribeQuery = onSnapshot(query, (snapshot) => {
setDocs(() => snapshot);
});
onCleanup(() => unsubscribeQuery());
return (
<div>
<For each={docs}>{(i) => <p>{i}</p>}</For>{" "}
</div>
);
};

When the components compiled, props becomes the function arguments and they are passed as is. So, if you pass readonly values to a component, they will remain readonly.
import { Component } from 'solid-js';
export const DocumentView: Component<any> = (props) => {
return (
<div>{JSON.stringify(props.o)}</div>
);
};
import { template as _$template } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/_$template(`<div></div>`, 2);
import { Component } from 'solid-js';
export const DocumentView = props => {
return (() => {
const _el$ = _tmpl$.cloneNode(true);
_$insert(_el$, () => JSON.stringify(props.o));
return _el$;
})();
};
Now, the problem with your code is using untracked reactive variables. By untract, we mean its dependencies can not be tracked by the Solid's runtime.
Solid tracks effects by creating a data structure where the inner scope will be owned by an outer scope, and this chain goes all the way up to the render function. That is why you get an error like below when you create effects outside this hierarchy.
SolidJS: "computations created outside
a `createRoot` or `render` will never be disposed."
This is for preventing memory leaks by releasing any resource created by an effect. You can read more about owners:
https://www.solidjs.com/docs/latest#runwithowner
However this check targets the untracked effects, not signals. So you can access signals anywhere, doesn't matter scope is tracked or not:
The related eslint rule limits the accessing of signals to the tracked scopes, probably as a safety measure.
"The reactive variable '{{name}}' should be used within JSX,
a tracked scope (like createEffect),
or inside an event handler function.",
This means you can use signal values inside a tracked scope. So, disabling the rule should not have any effect on how your components run.

Related

Global variables or singletons lose their value (reset) after refresh

I have a simple webpage that I would like to refresh every few minutes, defined by the user. So, for that I thought of using a global variable to store the value of how often the page should be refreshed. Unfortunately, it seems that after the page refreshes, all of the variable values are lost/restarted for some reason.
I tried using different kinds of imports, the singleton pattern, service pattern, a simple static variable inside a "Globals" class but nothing seems to work.
My current, relevant code for the global class is this:
class Globals {
static final Globals _instance = Globals._internal();
factory Globals() => _instance;
Globals._internal() {
_GLOBAL_REFRESH_MINUTES = 0;
}
int? _GLOBAL_REFRESH_MINUTES;
int get REFRESH_MINUTES => _GLOBAL_REFRESH_MINUTES!;
set REFRESH_MINUTES(int value) => _GLOBAL_REFRESH_MINUTES = value;
}
import 'package:myproject/common/globals.dart';
Globals _globals = Globals();
if (_globals.REFRESH_MINUTES > 0) {
new Timer.periodic(Duration(minutes: _globals.REFRESH_MINUTES),
(Timer t) => html.window.location.reload());
}
To solve your problem you need to save data to the browser, I could suggest hive: https://pub.dev/packages/hive this is great for things like 'remember me' on login or user preferences, configurations that need to persist across browser sessions.
Without knowing why you are refreshing the browser every few minutes, is it possible that another solution which does not globally restart the application could be leveraged? Perhaps whatever needs restarting could be disposed and reinitialized in its own class for example. If so that would be the correct approach to take.

Get semantic object and semantic action in controller

I'm trying to get the semantic object and semantic action of my deployed SAPUI5 application. I tried looking into ushell services - URLParsing and LaunchPage but it does not seem to return my semantic objects and actions.
Have anybody tried this?
This worked for me so far:
sap.ui.require([ // modules lazily instead of accessing them with global names.
"sap/ushell/library", // Consider adding `"sap.ushell": { lazy: true }` to dependencies in manifest.json
"sap/ui/core/routing/HashChanger",
], async (sapUshellLib, HashChanger) => {
const fullHash = new HashChanger().getHash(); // returns e.g. "masterDetail-display?sap-ui-theme=sap_fiori_3&/product/HT-1000"
const urlParsing = await sapUshellLib.Container.getServiceAsync("URLParsing");
urlParsing.parseShellHash(fullHash); /** returns e.g. {
action: "display",
appSpecificRoute: "&/product/HT-1000",
contextRaw: undefined,
params: { "sap-ui-theme": "sap_fiori_3" },
semanticObject: "masterDetail"
} **/
});
You can always just use
window.location.hash
Which you can parse yourself pretty easily. If you really want launchpad code, you can often find it here:
sap.ushell.services.AppConfiguration.getCurrentApplication().sShellHash
I've noticed it's not always set though when you're looking at an embedded component
A simplistic way to do this would be:
var oHashObject = new sap.ui.core.routing.HashChanger();
oHashObject.getHash();
//this will return the semantic object and action alongwith the routing params

Coffeescript "#" variables

What does it mean in Coffeescript when a variable name begins with an "#" sign?
For example, I've been looking through the hubot source code and just in the first few lines I've looked at, I found
class Brain extends EventEmitter
# Represents somewhat persistent storage for the robot. Extend this.
#
# Returns a new Brain with no external storage.
constructor: (robot) ->
#data =
users: { }
_private: { }
#autoSave = true
robot.on "running", =>
#resetSaveInterval 5
I've seen it several other places, but I haven't been able to guess what it means.
The # symbol is a shorcut for this as you can see in Operators and Aliases.
As a shortcut for this.property, you can use #property.
It basically means that the “#” variables are instance variables of the class, that is, class members. Which souldn't be confused with class variables, that you can compare to static members.
Also, you can think of #variables as the this or self operators of OOP languages, but it's not the exact same thing as the old javascript this. That javascript this refer to the current scope, which causes some problems when your are trying to refer to the class scope inside a callback for example, that's why coffescript have introduced the #variables, to solve this kind of problem.
For example, consider the following code:
Brain.prototype = new EventEmitter();
function Brain(robot){
// Represents somewhat persistent storage for the robot. Extend this.
//
// Returns a new Brain with no external storage.
this.data = {
users: { },
_private: { }
};
this.autoSave = true;
var self = this;
robot.on('running', fucntion myCallback() {
// here is the problem, if you try to call `this` here
// it will refer to the `myCallback` instead of the parent
// this.resetSaveInterval(5);
// therefore you have to use the cached `self` way
// which coffeescript solved using #variables
self.resetSaveInterval(5);
});
}
Final thought, the # these days means that you are referring to the class instance (i.e., this or self). So, #data basically means this.data, so, without the #, it would refer to any visible variable data on scope.

what happens in react when setState with object instance of a class

I have this fiddle
let m = new Mine();
this.setState(m, () => {
console.log('1:', m instanceof Mine, m.x, m.meth);
// => 1: true 123 function meth() {}
console.log('2:', this.state instanceof Mine, this.state.x, this.state.meth);
// => 2: false 123 undefined
});
As you can see I create an instance of the Mine class and then set state in a react component with that instance.
I would expect this.state to contain exactly that instance but while the instance properties that are set in the constructor are available I can't access any of the class methods on that instance.
The test in the fiddle shows that this.state is not an instance of the class Mine.
Does anybody understand what is going on or is this unintended behavior?
After more investigation I found out the reason why that happens.
The function _processPendingState from react uses Object.assign to set the new state, so since the target object is a new object (different than what is passed to setState) the new state loses the quality of being an instance of the "Mine" class.
And because Object.assign only copies own enumerable properties from the sources to the target the new state also won't have the class methods.
If in the fiddle we replace the line...
let m = new Mine();
with...
let m = {x: 123};
Object.defineProperty(m, 'meth', {
enumerable: false,
get() { return function() {}; }
});
we still don't have the "meth" property on the resulting state. Even if "m" owns the "meth" property it is not enumerable.
The best solution is to surface the method as an arrow function:
class Blah {
constructor() {
// no definition here for surfacedMethod!
}
surfacedMethod = () => {
// do something here
}
}
Then you can set instances of this class in setState and use their methods as if they were attributes set on the instance.
// other component innards
this.setState(state => ({blah: new Blah()}))
// later
this.state.blah.surfacedMethod(); // this will now work
In such case use replaceState, it should work.

Default values for missing parameters

I'm using Knockout with jQuery and jQuery templates. Assume that I have a template which expects a person object
<script type="text/html" id="person_template">
<tr><td>Forename</td><td><input type="textbox" data-bind="value:FORENAME" /></td></tr>
<tr><td>Surname</td><td><input type="textbox" data-bind="value: SURNAME"/></td></tr>
</script>
Now, if I pass an object with just a FORENAME to this template, I will get an error:
SURNAME is not defined error
I tried to create a custom binding in Knockout, but the error is thrown before it even gets there.
If I fill in these empty fields before passing the object to the template, I know everything will work out, but I would like to have the solution in my template rather than in my javascript.
Does anyone know a method that might help for situations like these?
This is a bit challenging, because you are within a template. While preparing the template, KO accesses the variable (well, actually it is accessed in jQuery Templates by a function that KO built).
One option is to pass your property as a string to a custom binding and make sure that it is initialized.
It would be like:
ko.bindingHandlers.valueWithInit = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var value = valueAccessor();
if (!context[value]) {
context[value] = ko.observable();
}
var realValueAccessor = function() {
return context[value];
}
//call the real value binding
ko.bindingHandlers.value.init(element, realValueAccessor, allBindingsAccessor, context);
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
var realValueAccessor = function() {
return context[valueAccessor()];
}
//call the real value binding
ko.bindingHandlers.value.update(element, realValueAccessor);
}
}
So, this would validate that your object has the field, if it does not it creates a new observable for that field. Then, it hands it off to the real value binding.
A very similar (but less verbose) alternative to this would be to have the binding ensure that the field is there and then rewrite the binding attribute to use the real value binding. Something like:
//Another option: rewrite binding after making sure that it is initialized
ko.bindingHandlers.valueWithInit = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var value = valueAccessor();
if (!context[value]) {
context[value] = ko.observable();
}
$(element).attr("data-bind", "value: " + value);
ko.applyBindings(context, element);
}
}
Both of these assume that the field that you are passing is directly off of the object that is the context of your template (so, it wouldn't work if you passed something with global scope like 'viewModel.someProperty').
Here is a working sample with both options: http://jsfiddle.net/rniemeyer/dFSeB/
I would rather not pass the field as a string, but there is not really a good way around it that I see.
You'll be better off ensuring that the object passed to the template has all the parameters set in. If they are not then you can add default values but putting all this logic in the template is going against the MVVM pattern. The templates (like Views in mvc) are not supposed to contain any logic IMO.