I have an object State().score that should be updated with it's added value when it is being called in Handler().addToScore(). The thing is that it always stays at it's initial value which is 0.
const DOM = () => {
const dom = {}
dom.score = document.getElementsByClassName('score')[0]
return dom
}
const State = () => {
const state = {}
state.score = 0 // This remains 0, I want it to update when adding to it
return state
}
const Handler = () => {
const handler = {}
handler.addToScore = function() {
State().score += 10
console.log(State().score) // Equals to 0 on every click and never gets updated
DOM().score.innerHTML = State().score
}
return handler
}
function checkLoginState() {
FB.getLoginStatus(function(response) {
statusChangeCallback(response);
});
}
function statusChangeCallback(response) {
if(response.status === 'connected') {
console.log( 'Logged in and authenticated' )
Handler().addToScore()
} else {
console.log('Not authenticated')
}
}
Every-time you run State() it sets returns a fresh object with score being 0
You will have to save the outcome of the intialization of your State().score if you want it to be saved. Or, you can change up the way that you're generating score by using a getter and a setter
const State = () => {
const state = {}
state.score = 0 // This remains 0, I want it to update when adding to it
return state
}
console.log(State().score); // State() is a function that ALWAYS returns { score: 0 }
let savedScore = State().score;
savedScore += 1;
console.log(savedScore);
Example using get/set (there's multiple ways to do this:
https://jsfiddle.net/mswilson4040/1ds8mbqw/3/
class State {
constructor() {
this._score = 0;
}
get score() {
return this._score;
}
set score(val) {
this._score = val;
}
}
const state = new State();
console.log(state.score);
state.score += 1;
console.log(state.score);
One other way to do this, of course, it to not make State a function. It looks like you're actually trying to manage a score or state so having State be a function that ultimately gives you a brand new state (score) everytime isn't going to work.
Something as simple as just not having State be a function would also work:
const State = () => {
const state = {}
state.score = 0 // This remains 0, I want it to update when adding to it
return state
}
Should be
const State = {
score: 0
};
Related
I have an array of objects. Each object has a method that should update a boolean property in the same object called 'found'.
When I call the function, the property does not update. I am not sure why.
I thought that the 'found' property would be accessible but it isn't??
I have created a minimal version of the problem here:
https://codepen.io/sspboyd/pen/XWYKMrv?editors=0011
const gen_s = function () { // generate and return the object
let found = false;
const change_found = function () {
found = true;
};
const update = function () {
change_found();
};
return {
change_found,
found,
update
};
};
const s_arr = []; // initialize an array
s_arr.push(gen_s()); // add a new s object to the array
console.log(s_arr[0].found); // returns 'false'
s_arr.forEach((s) => {
s.update();
});
console.log(s_arr[0].found);
When your change_found function changes the value of found, it's changing the value pointed to by your let found variable, but the object returned by your gen_s function still points to the old value.
You can fix your code using the 'holder' pattern, like this:
const gen_s = function () { // generate and return the object
let foundHolder = {value: false};
const change_found = function () {
foundHolder.value = true;
};
const update = function () {
change_found();
};
return {
change_found,
foundHolder,
update
};
};
const s_arr = []; // initialize an array
s_arr.push(gen_s()); // add a new s object to the array
console.log(s_arr[0].foundHolder.value); // returns 'false'
s_arr.forEach((s) => {
s.update();
});
console.log(s_arr[0].foundHolder.value);
Or even better, use a class:
class S {
constructor() { this.found = false; }
change_found() { this.found = true; }
update() { this.change_found(); }
}
const s_arr = [];
s_arr.push(new S());
console.log(s_arr[0].found);
s_arr.forEach(s => s.update());
console.log(s_arr[0].found);
I'm working on a pet project, a little front-end library for students. It reads variables/code in a JS file and tests it, outputting some panels. The code itself roughly follows the Jest framework.
My problem is that I'm trying to create a function that watches the execution of other functions, counts them, and lets me access the count.
function watchFunction(funcName){
let originalFunction = window[funcName];
let counter = 0;
// Wrap the function, counts when called
window[funcName] = function(...args){
console.log("watching");
counter++;
return originalFunction(...args);
}
return {
getCount: () => {return counter},
reset: () => {
// Unwrap the function
window[funcName] = originalFunction
}
}
}
This seems to work for methods like Number() or parseInt(), but I don't know how I would go about accessing methods like Math.floor(), or prototype methods like Array.prototype.map().
I've tried passing in the function reference instead of using window["funcNameString"], but that doesn't seem to work.
Does anyone have suggestions or tips for wrapping functions or watching functions like this?
EDIT:
It appears a solution was found!
function watchFunction(obj, fName) {
let counter = 0;
const originalFunction = obj[fName];
obj[fName] = (...args) => {
counter++;
return originalFunction.bind(obj)(...args);
};
return {
removeWatcher: () => (obj[fName] = originalFunction),
resetCount: () => (counter = 0),
getCount: () => counter,
};
}
Example of use:
// Array.prototype.push
const arrayPushWatcher = watchFunction(Array.prototype, "push");
let arr = [];
// 0
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);
// 1
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);
// 2
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arrayPushWatcher.removeWatcher();
arr.push(1);
// 2 (stopped counting)
console.log("Array.prototype.push", arrayPushWatcher.getCount());
How to watch for any function call
Is that what you want? I can also write a block for this function so that it determines whether an object has been passed in or a string. If string -> run this function on window as a property "objectThatStoresFunction".
I've tried playing around with the Function.prototype, but it doesn't really work. So the function turned out a bit more complex.
This code below works both with functions / objects on window Array.prototype.map (Prototype / Class functions)
function watchFunction(objectThatStoresFunction, functionName) {
let counter = 0;
const originalFunction = objectThatStoresFunction[functionName];
objectThatStoresFunction[functionName] = (...args) => {
counter += 1;
return originalFunction(...args);
}
return {
getCount: () => {
return counter
}
}
}
const mathRoundWatcher = watchFunction(Math, 'round');
// 0
console.log(mathRoundWatcher.getCount());
// 1
Math.round(99666.9999999);
console.log(mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log(mathRoundWatcher.getCount());
function watchFunction(objectThatStoresFunction, functionName, optionalOriginalFunction) {
const self = this;
if (optionalOriginalFunction) {
objectThatStoresFunction = this.window;
functionName = optionalOriginalFunction.name;
}
let counter = 0;
const originalFunction = objectThatStoresFunction[functionName] || optionalOriginalFunction;
objectThatStoresFunction[functionName] = (...args) => {
counter += 1;
return originalFunction.bind(self)(...args);
}
return {
// should it remove the watcher or reset the count?
reset: () => objectThatStoresFunction[functionName] = originalFunction,
getCount: () => {
return counter;
}
}
}
const arrayMapWatcher = watchFunction(Array.prototype, 'map');
// 0
console.log('Array.prototype.map', arrayMapWatcher.getCount());
[-99].map(() => {});
// 1
console.log('Array.prototype.map', arrayMapWatcher.getCount());
const mathRoundWatcher = watchFunction(Math, 'round');
// 0
console.log('Math.round', mathRoundWatcher.getCount());
// 1
Math.round(99666.9999999);
console.log('Math.round', mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log('Math.round', mathRoundWatcher.getCount());
const alertWatcher = watchFunction(null, null, window.alert);
// 0
console.log('window.alert', alertWatcher.getCount());
// 1
window.alert('1');
console.log('window.alert', alertWatcher.getCount());
// 2
alert('2')
console.log('window.alert', alertWatcher.getCount());
// reset the alertWatcher counter
alertWatcher.reset();
This code above breaks the stacksnippets.com when used with Array.prototype.map for some reason, please see this JsFiddle link:
https://jsfiddle.net/ctbjnawz/3/
Do you mean a method of an instance or object? One way is to create a new function. e.g
function WatchInstanceMethods(instance, functionName){
let originalFunction = window[instance][funcName];
let counter = 0;
window[instance][functionName] = function(...args){
console.log("watching");
counter++;
return originalFunction(...args);
}
return {
getCount: () => {return counter},
reset: () => {
// Unwrap the function
window[funcName] = originalFunction
}
}
}
although adding support for chaining methods will get difficult with more nested methods but you can pass a string for functionName name and split it to have each layer of calling instance for function and repeat the logic above.
Up front I'm new to Node / Javascript an. What I am trying to do is to add a logging to my repository using a decorator function. Therefor I'm trying to iterate though each function from inside the constructor and override it with something like: "
Object.getOwnPropertyNames(Repository.prototype).forEach((func) => this.decorator(func));"
My problem is that "Object.getOwnPropertyNames" only returns the function names instead of the actual function. Is there a way to apply this decorator to each function?
"use strict"
const db = require("./Database/db_operations");
const logger = require("./utils/logger")
const {createTables} = require("./Database/db_operations");
const loggingTypes = require("./utils/logginTypes")
class Repository {
async saveTermin(Termin) {
}
async saveToDo(toDo) {
return await db.saveToDo(toDo);
}
async saveAppointment(Appointment) {
return await db.saveAppointment(Appointment);
}
async updateAppointment(Appointment) {
return await db.updateAppointment(Appointment);
}
async deleteAppointment(uuid) {
return await db.deleteAppointment(uuid);
}
async saveAppointmentParticipants(appointment) {
return await db.saveAppointmentParticipants(appointment);
}
async saveAppointmentFiles(appointment) {
return await db.saveAppointmentFiles(appointment)
}
async getAppointmentFiles(appointment) {
return await db.getAppointmentFiles(appointment)
}
async deleteToDo(todo) {
return await db.deleteToDo(todo)
}
}
// All functions will be mapped to there type to optimize logging. If a function is not mapped to its type,
// it will be automaticly assigned to the "unspecified type". Logging will still work, but depending on what
// arguments are given and what is returned, the output might not perfectly fit
const funcMapping = new Map();
// GET
funcMapping.set(Repository.prototype.getAppointmentFiles, loggingTypes.GET);
funcMapping.set(Repository.prototype.getAllDatas, loggingTypes.GET);
funcMapping.set(Repository.prototype.getAllToDos, loggingTypes.GET);
//SAVE
funcMapping.set(Repository.prototype.saveToDo, loggingTypes.SAVE);
funcMapping.set(Repository.prototype.saveAppointment, loggingTypes.SAVE);
funcMapping.set(Repository.prototype.saveAppointmentParticipants, loggingTypes.SAVE);
//DELETE
funcMapping.set(Repository.prototype.deleteAppointment, loggingTypes.DELETE);
funcMapping.set(Repository.prototype.deleteToDo, loggingTypes.DELETE);
Object.getOwnPropertyNames(Repository.prototype)
.forEach(name => {
const func = Repository.prototype[name];
// checking loggingTypes - if no type is assigned function will be assigned to "UNASSIGNED".
// console.log(funcMapping.has(func) +" "+ func.name)
if (!funcMapping.has(func)) {
funcMapping.set(func, loggingTypes.UNASSIGNED);
}
// function will only be wrapped if logging is enabled.
if (funcMapping.get(func)[1]) {
Repository.prototype[name] = async function (...args) {
// calls the original methode
const returnValue = await func.apply(this, args);
const argumentsInArray = Array.prototype.slice.call(args);
// Put any additional logic here and it will be applied -> magic
// Logging
db.writeLogging(logger(func, returnValue, funcMapping.get(func)[0]), args).then(() => {
console.log(`Function "${name}()" was successfully logged and saved to Database`)
}).catch(e => {
console.log(`Function "${name}()" could not be logged and saved to Database. ${func}`)
console.log(e)
})
return returnValue;
}
}
});
module.exports = new Repository();
const appointment_model = require('../models/Appointment');
const contact_model = require('../models/Contact');
const toDo_model = require('../models/ToDo');
const file_model = require('../models/File');
const loggingTypes = require("./logginTypes")
function log() {
// returns a function that returns an object. When this function is then called the object is returned
return function decorator(funcToLog, returnValue, funcType, ...args) {
// console.log("arguments in logger" + args);
// create prototype for object that later will be passed to database
const descriptor = function (user, change, changedAt) {
this.user = user; // some user id
this.change = change; //
this.changedAt = changedAt; // date when changes occoured
this.appointmentId = getUuid(appointment_model);
this.todoId = getUuid(toDo_model);
this.contactId = getUuid(contact_model);
this.fileId = getUuid(file_model);
};
// contains all logging Data about the function beeing called -> name of function, usedArguments and returnValue
function getChanges(func, funcType, returnValue, args) {
let changes = null;
switch (funcType) {
case loggingTypes.GET[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
break;
case loggingTypes.SAVE[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args}, // ?
newData: returnValue // could call function here
}
break;
case loggingTypes.UPDATE[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
break;
case loggingTypes.DELETE[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
break;
case loggingTypes.UNASSIGNED[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
}
return changes;
}
function getUuid(model_type) {
let uuid = null;
console.log(args)
for (let i = 0; i < args.length; i++) {
console.log(args[i])
if (args[i] instanceof model_type) {
uuid = parseInt(args[i].uuid);
}
return uuid;
}
}
return new descriptor("someUserId", JSON.stringify(getChanges(funcToLog, funcType, returnValue, args)), new Date())
}
}
module.exports = log();
You can easily map function names to their values using an intermediate step:
Object.getOwnPropertyNames(Repository.prototype)
.map(name => Repository.prototype[name])
.forEach((func) => this.decorator(func));
Anyway, the constructor is not the best place to do this, because you would end up applying the decorator every time a new instance of the class is created.
I would rather move the whole decorator logic after the class definition, before the assignment to module.exports.
Object.getOwnPropertyNames(Repository.prototype)
.forEach(name => {
const func = Repository.prototype[name];
Repository.prototype[name] = function (...args) {
console.log("Decorator was called");
const returnValue = func.apply(this, args);
// Put additional logging logic here...
return returnValue;
}
});
Update
In response to what noted in the comments, here is a somewhat more robust version of the code above, with additional precautions you may or may not need:
Preserve non-functions
Preserve non-value properties
Preserve the constructor
Preserve non-configurable properties
Include properties with symbol keys
Reflect.ownKeys(Repository.prototype).forEach(key => {
const descriptor = Reflect.getOwnPropertyDescriptor(Repository.prototype, key);
if (!descriptor.configurable) return;
const { value } = descriptor;
if (typeof value !== 'function') return;
if (value === Repository) return;
descriptor.value = function (...args) {
console.log("Decorator was called");
const returnValue = value.apply(this, args);
// Additional logging logic here...
return returnValue;
};
Object.defineProperty(Repository.prototype, key, descriptor);
});
Another thing I left out is additional logic to make sure that the decorated methods have the same length and name properties and the same prototype as the original functions. You may want to adjust even more details as you discover additional requirements while using your code.
I'm trying to call my action via props in Next.js, but it keeps exceeding maximum depth and I couldn't understand why.
UNSAFE_componentWillReceiveProps(nextProps) {
let rate_updated = [...this.props.previousArray];//copying store value to local variable
for (let i = 0; i < this.props.crypto_head_coins.length; i++) {
//my code here
}
this.props.update_rate_array(rate_updated) // calling action to set array value in state
this.setState({ rate_updated }); // setting array to state
}
this is how I'm mapping them
const mapDispatchToProps = dispatch => ({
update_rate_array: Array => dispatch(update_rate_array(Array))
});
const mapStateToProps = state => ({
previousArray: state.crypto_head_update
});
Check if this.props equals nextProps as follows and execute only when they are different.
UNSAFE_componentWillReceiveProps(nextProps) {
if(this.props !== nextProps){
let rate_updated = [...this.props.previousArray];//copying store value to local variable
for (let i = 0; i < this.props.crypto_head_coins.length; i++) {
//my code here
}
this.props.update_rate_array(rate_updated) // calling action to set array value in
state
this.setState({ rate_updated }); // setting array to state
}
}
I have an array of 16 objects which I declare as a state in the constructor:
this.state = {
todos:[...Array(16)].map((_, idx) => {
return {active: false, idx}
}),
}
Their status will get updated through an ajax call in ComponentDidMount.
componentDidMount()
{
var newTodos = this.state.todos;
axios.get('my/url.html')
.then(function(res)
{
newTodos.map((t)=>{
if (something something)
{
t.active = true;
}
else
{
t.active = false;
}
}
this.setState({
todos:newTodos,
})
}
}
and then finally, I render it:
render(){
let todos = this.state.todos.map(t =>{
if(t.active === true){
console.log('true'}
else{
console.log('false')
}
})
return (
<div></div>
)
}
They all appear as active = false in the console, they never go into the if condition. When
I print out the entire state it appears not to be updated in the render method. In the console it says "value below was just updated now".
I thought changes to the state in ComponentWillMount will call the render function again?
How do I make that React will accept the new values of the state?
componentDidMount()
{
var newTodos = []; // <<<<<<<<<<<<<
axios.get('my/url.html')
.then(function(res)
{
newTodos = this.state.todos.map((t)=>{ //<<<<<<<<<<<<<<<
if (something something)
{
t.active = true;
}
else
{
t.active = false;
}
return t; //<<<<<<<<<<<<<<<<<<
} // <<<<< are you missing a semi-colon?
this.setState({
todos:newTodos,
})
}
}
The map() argument (in your code) is a function, not an expression, so an explicit return must be provided. I.E.:
xxx.map( t => ( "return t is implicit" ) );
xxx.map( t => { "return t must be explicit" } );
And, as #DanielKhoroshko points out, your new variable points to this.state. And of course never, never, ever alter this.state directly. Since map() returns a new array, not the original as altered, that's why we use map() and not forEach()
That is because you are actually not providing any new state, but mutating it instead.
React uses shallow comparison be default (where to objects are equal if they reference the same memory address). And that's exactly what's happening here:
var newTodos = this.state.todos; // newTodos === this.state.todos
this.setState({ todos:newTodos }) // replaces two equal addresses, so it simply doesn't change anything
The easiest solution, though probably not the most performant would be to clone your todos array:
var newTodos = [...this.state.todos]; // newTodos !== this.state.todos