Why does this getter method has been called several times in React? - javascript

I have a Store which will be provided to the component. In this Store file, there are several getter function. But I find only this getter function will be executed three times since this.rawMonthlyImpacts will be only changed once when the api get response from backend. I am so confused because other getter function in this file will be only executed once. During every execution, this.rawMonthlyImpacts is always same. Because this function is time-consuming, so I want to figure out why this happens. Hope you can give me some advice. Thanks!
get Impacts(){
const monthlyImpacts = new Map<string, Map<string, number>>();
if (this.rawMonthlyImpacts) {
this.rawMonthlyImpacts.forEach((impact) => {
if (impact.Impact > 0) {
const month = TimeConversion.fromTimestampToMonthString(impact.Month);
const tenantId = impact.TenantId;
const tenantImpact = impact.Impact;
if (!monthlyImpacts.has(month)) {
const tenantList = new Map<string, number>();
monthlyImpacts.set(month, tenantList.set(tenantId, tenantImpact));
} else {
const tenantWithImpactMap = monthlyImpacts.get(month);
if (!tenantWithImpactMap.has(tenantId)) {
tenantWithImpactMap.set(tenantId, tenantImpact);
} else {
tenantWithImpactMap.set(tenantId, tenantWithImpactMap.get(tenantId) + tenantImpact);
}
monthlyImpacts.set(month, tenantWithImpactMap);
}
}
});
}
return monthlyImpacts;
},
Update: I have find that there are other two functions use this.Impacts. If I remove these two functions, the getter function will only be executed only once. I think the getter function uses the cache to store data, so once the data is calculated for the first time, subsequent calls to the getter function should not be re-executed, only the value in the cache needs to be retrieved. So I am very confused about why this getter function will be executed 3 times.
getImpactedTenants(month: string): string[] {
return Array.from(this.Impacts.get(month).keys());
},
get overallMonthlyImpactedTenants(): Map<string, number> {
return new Map<string, number>(
Array.from(this.Impacts)?.map((monthEntries) => {
const month = monthEntries[0];
const impactedTenants = monthEntries[1].size;
return [month, impactedTenants];
})
);
}

Hard to tell what exactly is happening without more context, but remember that with a get function, every single time you reference that property (.Impacts in this case) the get function will be called.
Assuming that each impact stored in this.rawMonthlyImpacts which you loop through is an instance of the class with this getter, then as far as I'm aware, you are calling the get function each time you reference impact.Impacts, such as in the conditional:
if (impact.Impact > 0) {
I might be way off though; I'm unfamiliar with React and so my answer is based only on my experience with vanilla JS.

Related

Detect when svelte store is not used anymore

I'm making a custom svelte store by wrapping around a svelte writable store.
I want to detect when that store is not subscribed by any component; when the subscription count is 0
My objective is to clear some heavy external resources (websockets) that were tied to the custom store when no one is using it.
Currently, I'm counting the subscriptions and unsubscriptions by wrapping around subscribe( ) method. It works as expected. But It looks like a nasty hack to me.
My question: Is there a standard / clean way to achieve this behavior in Svelte?
If not, can someone with more experience in Javascipt and svelte confirm whether this is legit?
Demo on : https://svelte.dev/repl/f4e24fb5c56f457a94bf9cf645955b9f?version=3.43.1
import { writable } from 'svelte/store';
// Instanciate the store
export let store = MakeStore();
// By design, I want a function that returns a custom svelte store
export function MakeStore(initialValue = null) {
const { subscribe, set, update } = writable(initialValue);
let subscribercount = 0;
let wsubscribe = function (run, callback) {
subscribercount++;
console.log("subscribercount++", subscribercount);
let wunsubscribe = subscribe(run, callback);
return () => {
subscribercount--;
console.log("subscribercount--", subscribercount);
if (subscribercount == 0) {
// -------------------------------
// Free up resources
// I want a clean way to get here
// -------------------------------
console.log("Cleaning up...");
}
return wunsubscribe();
}
}
// Some external calls here
let store = {
subscribe: wsubscribe,
set: newvalue => {
set(newvalue);
// Some external calls here
},
update: update
};
// Some external calls here
return store;
}
Yes, it's built into the store and documented here
from the docs
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a set function which changes the value of the store. It must return a stop function that is called when the subscriber count goes from one to zero.
so you would do for example:
const count = writable(0, () => {
console.log('got a subscriber');
return () => console.log('no more subscribers');
});
Update 8 Feb 2023
Note that the above works for both readable and writable stores, in the case of derived stores where you would have the following code:
const count = derived(items, ($items, set) => {
console.log('got a subscriber to a derived store');
return () => console.log('no more subscribers to derived store');
});
Here it will log no more subscribers to derived store, when either the number of subscribers drops to 0 or when the original store changes (this is because this entire function ($items, set) => {...} runs again).
As of v3.55.1 there is no built in way to circumvent this.

Assign and Query Javascript Arrow Function for Metadata

The problem is rather simple. We need to imbue a function with a parameter, and then simply extract that parameter from the body of the function. I'll present the outline in typescript...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Let's set the scene:
Assume strict mode, no arguments or callee. Should work reasonably well on the recent-ish version of v8.
The function passed to assign() must be an anonymous arrow function that doesn't take any parameters.
... and it's alsoasync. The assigned value could just be stored somewhere for the duration of the invocation, but because the function is async and can have awaits, you can't rely on the value keeping through multiple interleaved invocations.
this.getAssignedValue() takes no parameters, returning whatever we assigned with the assign() method.
Would be great to find a more elegant solution that those I've presented below.
Edit
Okay, we seem to have found a good solid solution inspired by zone.js. The same type of problem is solved there, and the solution is to override the meaning of some system-level primitives, such as SetTimeout and Promise. The only headache above was the async statement, which meant that the body of the function could be effectively reordered. Asyncs are ultimately triggered by promises, so you'll have to override your Promise with something that is context aware. It's quite involved, and because my use case is outside of browser or even node, I won't bore you with details. For most people hitting this kind of problem - just use zone.js.
Hacky Solution 2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
In this solution, I'm making an object that overrides the getAssignedValue() method, and re-evaluates the source code of the passed function, effectively changing the meaning of this. Still not quite production grade...
Edit.
Oops, this breaks closures.
I don't know typescript so possibly this isn't useful, but what about something like:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
Hacky Solution 1
We actually have a working implementation. It's such a painful hack, but proves that this should be possible. Somehow. Maybe there's even a super simple solution that I'm missing just because I've been staring at this for too long.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
So the idea in our solution is to examine the stack trace of the new Error().stack, and wrap something we can extract as a token, which in turn we'll put into a repo. Hacky? Very hacky.
Notes
Testing shows that this is actually quite workable, but requires a more modern execution environment than we have - i.e. ES2017+.

Why immer.js doesn't allow setting dynamic properties on draft?

//I want my action to dispatch payload like
// {type:'update',payload:{'current.contact.mobile':'XXXXXXXXX'}}
//In reducer dynamically select the segment of state update needs to be applied to
//Below code doesn't work as expected though, draft always remains at same level
draft = dA.key.split('.').reduce((draft, k) => {
return draft[k]
}, draft);
//Or an ideal syntax may look like below line
draft['current.contact.mobile'] = dA.value;
//Code that works
draft['current']['contact']['mobile'] = dA.value;
I want my action to dispatch payload like
{type:'update',payload:{'current.contact.mobile':'XXXXXXXXX'}}
And in reducer dynamically select the segment of state that needs to be updated.
Is there something fundamentally wrong in doing this, I believe this could make life easier. Is there something that can done to achieve this ?
In your case, this code returns a primitive value like a string or number which is immutable.
draft = dA.key.split('.').reduce((draft, k) => {
return draft[k]
}, draft);
"Immer" is using Proxy to implement all this magic. The proxy could work only on objects for example Object, Array, Function etc.
so to fix your problem you can use code like this
import produce from "immer";
describe("Why immer.js doesn't allow setting dynamic properties on draft?", function() {
it("should allow set dynamic properties", function() {
const path = "foo.bar.zoo";
const state = { foo: { bar: { zoo: 1 } } };
const nextState = produce(state, draft => {
const vector = path.split(".");
const propName = vector.pop();
if (propName) {
draft = vector.reduce((it, prop) => it[prop], draft);
draft[propName] += 1;
}
});
expect(nextState.foo.bar.zoo).toEqual(state.foo.bar.zoo + 1);
});
});
In the code above, we get destination object and update the property of this object.
Some note about string and number.
Javascript has constructors for string and number which return objects not primitive values. But this is a very rare case when someone uses it explicitly.
Usually, we deal with it implicitly when writing something like this dA.key.split('.'). In this case, the interpreter would create a string object and call method "split" on it. Usually, this behavior is referred to as "Boxing"

How to include or detect the name of a new Object when it's created from a Constructor

I have a constructor that include a debug/log code and also a self destruct method
I tried to find info on internet about how to detect the new objects names in the process of creation, but the only recommendation that I found was pass the name as a property.
for example
var counter = {}
counter.a =new TimerFlex({debug: true, timerId:'counter.a'});
I found unnecessary to pass counter.a as a timerId:'counter.a' there should be a native way to detect the name from the Constructor or from the new object instance.
I am looking for something like ObjectProperties('name') that returns counter.a so I don't need to include it manually as a property.
Adding more info
#CertainPerformance What I need is to differentiate different objects running in parallel or nested, so I can see in the console.
counter.a data...
counter.b data...
counter.a data...
counter.c data... etc
also these objects have only a unique name, no reference as counter.a = counter.c
Another feature or TimerFlex is a method to self desruct
this.purgeCount = function(manualId) {
if (!this.timerId && manualId) {
this.timerId = manualId;
this.txtId = manualId;
}
if (this.timerId) {
clearTimeout(this.t);
this.timer_is_on = 0;
setTimeout ( ()=> { console.log(this.txtId + " Destructed" ) },500);
setTimeout ( this.timerId +".__proto__ = null", 1000);
setTimeout ( this.timerId +" = null",1100);
setTimeout ( "delete " + this.timerId, 1200);
} else {
if (this.debug) console.log("timerId is undefined, unable to purge automatically");
}
}
While I don't have a demo yet of this Constructor this is related to my previous question How to have the same Javascript Self Invoking Function Pattern running more that one time in paralel without overwriting values?
Objects don't have names - but constructors!
Javascript objects are memory references when accessed via a variables. The object is created in the memory and any number of variables can point to that address.
Look at the following example
var anObjectReference = new Object();
anObjectReference.name = 'My Object'
var anotherReference = anObjectReference;
console.log(anotherReference.name); //Expected output "My Object"
In this above scenario, it is illogical for the object to return anObjectReference or anotherReference when called the hypothetical method which would return the variable name.
Which one.... really?
In this context, if you want to condition the method execution based on the variable which accesses the object, have an argument passed to indicate the variable (or the scenario) to a method you call.
In JavaScript, you can access an object instance's properties through the same notation as a dictionary. For example: counter['a'].
If your intent is to use counter.a within your new TimerFlex instance, why not just pass counter?
counter.a = new TimerFlex({debug: true, timerId: counter});
// Somewhere within the logic of TimerFlex...
// var a = counter.a;
This is definitely possible but is a bit ugly for obvious reasons. Needless to say, you must try to avoid such code.
However, I think this can have some application in debugging. My solution makes use of the ability to get the line number for a code using Error object and then reading the source file to get the identifier.
let fs = require('fs');
class Foo {
constructor(bar, lineAndFile) {
this.bar = bar;
this.lineAndFile = lineAndFile;
}
toString() {
return `${this.bar} ${this.lineAndFile}`
}
}
let foo = new Foo(5, getLineAndFile());
console.log(foo.toString()); // 5 /Users/XXX/XXX/temp.js:11:22
readIdentifierFromFile(foo.lineAndFile); // let foo
function getErrorObject(){
try { throw Error('') } catch(err) { return err; }
}
function getLineAndFile() {
let err = getErrorObject();
let callerLine = err.stack.split("\n")[4];
let index = callerLine.indexOf("(");
return callerLine.slice(index+1, callerLine.length-1);
}
function readIdentifierFromFile(lineAndFile) {
let file = lineAndFile.split(':')[0];
let line = lineAndFile.split(':')[1];
fs.readFile(file, 'utf-8', (err, data) => {
if (err) throw err;
console.log(data.split('\n')[parseInt(line)-1].split('=')[0].trim());
})
}
If you want to store the variable name with the Object reference, you can read the file synchronously once and then parse it to get the identifier from the required line number whenever required.

Passed-in Values Not Available at Run-time of Function in Angular App

I realize there is something I'm missing in terms of how and specifically when the products of certain functions are available in JavaScript.
In my Angular app, in order to get a user's initials, I am parsing data being returned from the API, and retrieving the first letter of the firstName, as well as the first letter of lastName in two different functions. These two functions are working as expected, and I can see the correct results in the console:
getFirstNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const firstName = userInfo.name.first;
const firstNameFirstLetter = firstName.trim().charAt(0);
console.log(firstNameFirstLetter);
return firstNameFirstLetter;
}
}
getLastNameFirstLetter() {
if (this.authenticationService.isAuthenticated()) {
const userObj = JSON.parse(sessionStorage.getItem('currentUser'));
const userInfo = userObj.data;
const lastName = userInfo.name.last;
const lastNameFirstLetter = lastName.trim().charAt(0);
console.log(lastNameFirstLetter);
return lastNameFirstLetter;
}
}
Now comes the part I'm not fully understanding. When I then pass the returned values of these two functions, in order to get the initials, like this:
getInitials(firstNameFirstLetter, lastNameFirstLetter) {
if (this.authenticationService.isAuthenticated()) {
if (!this.firstNameFirstLetter || !this.lastNameFirstLetter) {
console.log('Names not ready!');
return;
} else if (this.firstNameFirstLetter && this.lastNameFirstLetter) {
console.log(firstNameFirstLetter + lastNameFirstLetter);
return firstNameFirstLetter + lastNameFirstLetter;
}
}
}
... I get "Names not ready!" printed to the console each time.
By the way, I am running these functions within Angular's ngOnInit life cycle hook, like this:
ngOnInit() {
this.getFirstNameFirstLetter();
this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
I know this has something to do with what's available when, because I get 'undefined' when I use break points and debug the two values being passed into the "getInitials()" function. In other words, the function doesn't have access to the returned values of the other two functions at the time it's run -- hence I'm getting 'Names not ready!' printed to the console. My question is, what am I missing, architecturally, to resolve this kind of issue?
So what is happening here is that JavaScript doesn't think you are using the return values for getFirstNameFirstLetter and getLastNameFirstLetter, so when it makes the call, instead of waiting for that call to finish, it goes on to the next one, which introduces a race condition. if you simply change it to
ngOnInit() {
let temp1 = this.getFirstNameFirstLetter();
let temp2 = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
then it will wait for the previous functions to finish before calling the next.
Also, I don't use const very often, so I could be wrong and it could follow different scope rules, but by normal scope rules, setting a variable in that function, it is only available in that function, you would need to set it as
this.firstNameFirstLetter = firstName.trim().charAt(0);
to have access to it outside the function.
Or, so as to kill two birds with one stone, you could do
ngOnInit() {
this.firstNameFirstLetter = this.getFirstNameFirstLetter();
this.lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(this.firstNameFirstLetter, this.lastNameFirstLetter);
}
or
ngOnInit() {
let firstNameFirstLetter = this.getFirstNameFirstLetter();
let lastNameFirstLetter = this.getLastNameFirstLetter();
this.getInitials(firstNameFirstLetter, lastNameFirstLetter);
}
depending on if you need the variables again or just for that function.

Categories

Resources