I am trying to refactor some code and wanted to use destructuring to make the code a bit easier to read. I am passing in an object into the remove() function, however one of the variables is returning:
'inStock' is assigned a value but never used no-unused-vars
According to the eslint docs:
'A read for a modification of itself is not considered as used.'
https://eslint.org/docs/rules/no-unused-vars
I was wondering whether if there's a way to resolve this issue without:
modifying eslint (no-unused-vars) ?
adding /* eslint-disable no-unused-vars */ comments inline ?
Thanks in advance
before refactoring:
remove(product) {
if (product.quantity > 0) {
product.quantity --;
product.inStock ++;
}
}
after refactoring:
remove({ quantity, inStock }) {
if (quantity > 0) {
quantity --;
inStock ++;
}
}
Problem
Your refactor is based on the misunderstanding that quantity-- and inStock++ has the same behaviour as product.quantity-- and product.inStock++. The core of this misunderstanding is thinking that the destructured fields still refer to the fields on the object. Let's take a look at what destructuring really is.
Take this example, with destructuring:
const obj = { foo: 0 };
const { foo } = obj;
Without destructuring, it'd look like this:
const obj = { foo: 0 };
const foo = obj.foo;
The destructuring syntax is just a shortcut of the second example.
What this illustrates is destructuring defines a new variable and assigns the value (not the reference) of the field you're destructuring to that variable. When you make mutations to that variable, you'll only mutate the value of the variable but not the value of the object field. Here's a runnable example, you'll see that obj.foo hasn't changed from 0, even though we increment foo which has been destructured from obj:
const obj = { foo: 0 };
var { foo } = obj;
foo++;
console.log(obj); // { "foo": 0 }
Going back to your linting error: the linter is correct, not only raising the fact that the variable is unused but revealing this problem that I've explained.
Solution
There are two immediate solutions I can think of:
Don't destructure. With your example, while destructuring does shorten the amount of code there is, it could introduce confusion as to what is mutated and make the code less understandable – you've experienced it yourself.
Reassign the incremented/decremented value back to your object. You'll need to prefix the operator to the operand because this returns the value after the operation. Postfixing returns the value before the operation (aka the original value).
remove({ quantity, inStock }) {
if (quantity > 0) {
product.quantity = --quantity;
product.inStock = ++inStock;
}
}
If you ever learn about functional programming, specifically pure functional programming, there might be other patterns you can apply to your scenario but that's beyond the scope of this answer. These patterns won't do mutations which could make it easier to understand how data flows through and is changed by your system.
Related
I'm building a web framework, something like React; one of the things which I would like to improve on React is state.
My idea is something like Svelte, to use state you just create a normal variable (in my case it would be okay to use a function when creating te state, but not when updating it), but how Svelte does this Magic is by compiling, and I would like it to work in vanilla Javascript.
From my understanding this is not exactly possible, but I've still been trying to hack something somehow.
So the part of this state system that is not possible is knowing when a primitive is set and got (setters & getters), I want it to work with scoped variables; so I can't use the Object.defineProperty on the window or globalThis. I've been hacking around for quite some time and here are the only solutions I thought have could worked:
Proxing a new String(string), has given weird error of this beeing of the wrong type, unknows values, and stuff.
Proxing the Funtion.arguments object, but this didn't work.
Using Symbol.toPrimitive, but I couldn't find a way of using it without a + or ${}.
But as you can see they all have problems, I'm stuck and can't find anything, is there any (even if hacky, though without legacy or deprecated code) way to do this? Thank you!
You can't do what you've described in JavaScript. You can't proxy a primitive, and you can't run code some other way (getter, setter) when a variable is read or set, only when a property of an object is read or set.
There is an awful thing you can do in loose mode that's disallowed (for good reasons) in strict mode where you have an object with getters and setters that you then put into the environment used for resolving freestanding identifiers using the with statement, but again, it's disallowed for good reasons in strict mode (which is the default for modules and other mechanisms that create new contexts, like the body of a class).
I hesitate to give an example of it, but for completeness:
// This only works in loose mode, not strict mode
let a = 0;
const obj = {
get a() {
console.log(`Getter called, returning a = ${a}`);
return a;
},
set a(value) {
console.log(`Setter called, setting a = ${value}`);
a = value;
}
};
with (obj) {
console.log(a);
a = 42;
console.log(a);
}
Re your updated question:
My idea is something like Svelte, to use state you just create a normal variable...but how Svelte does this this Magic is by compiling, and I would like it to work in vanilla Javascript.
I wouldn't try to do it with freestanding variables, have the user provide a state object and convert its data properties to getter/setter combinations (or replace it with a new version with getter/setter combinations, etc.):
// Userland code provides a state object
const state = {
a: 0,
b: "hi",
};
// Your framework code converts it to using getters/setters
function enhance(obj) {
const descrs = Object.getOwnPropertyDescriptors(obj);
for (const key of Object.keys(descrs)) {
const descr = descrs[key];
if (descr.configurable && "value" in descr && typeof descr.value !== "function") {
// A simple data property; wrap it in getter/setter
let value = descr.value;
if (typeof value === "object") {
enhance(value);
} else {
Object.defineProperty(obj, key, {
get() {
console.log(`Getter called, returning ${key} = ${value}`);
return value;
},
set(newValue) {
console.log(`Setter called, setting ${key} = ${newValue}`);
value = newValue;
},
enumerable: descr.enumerable,
configurable: true,
});
}
}
}
}
enhance(state);
// Their code using the properties triggers your getter/setters:
console.log(state.a, state.b);
state.a = 42;
state.b = state.b.toUpperCase();
console.log(state.a, state.b);
Sometimes I find myself needing to initialize an object with a property that matches the property of another object. When the property name is the same, I want to be able to use shorthand syntax.
(For the purposes of the examples in this question, I'll just keep the additional properties to a tag: 1 property, and I'll reuse message in subsequent examples as the input/source of the information. I also indicate an extra unwanted property of message because I'm cherry-picking properties and do not intend to just use Object.assign to assign all the properties of message to the result.)
const message = {
person: {
name: 'John'
},
unwanted: 'x'
};
let result = { person: message.person, tag: 1 }; // Looking for shorthand for this
To formulate the result object above, I needed to type person twice. I was thinking there must be a shorthand way to do this. My reasoning for expecting this to work is that features exist like ES2015 Shorthand property names and Destructuring assignment. e.g:
const { person } = message; // destructing assignment
let result = { person, tag: 1 }; // shorthand property name for `person`
This would create an extra variable called person that has the value of message.person, And the result would have a property called person that has the desired value. But if there's no variable already existing then I don't know how to use shorthand in this case. And I haven't found a way to apply these two syntactical features together.
This was my first intuitive guess at what the syntax would be:
// hoping destructuring assignment is allowed in object literal
let result = { {person} = message, tag: 1 }; // it is not legal :(
My second guess was this:
// hoping that a property name would magically be inferred from `person`
let result = { message.person, tag: 1 }; // it is not legal :(
As a last resort I tried Object.assign, but it copies unwanted properties and does not cherry-pick just the person property of message.
let result = Object.assign({ tag: 1 }, message); // Assigns unwanted properties :(
So the best I have so far is { person: message.person, tag: 1 }
Is there shorthand initializer syntax to achieve this?
The best I have so far is { person: message.person, tag: 1 }.
Is there shorthand initializer syntax to achieve this?
No, this is still they way to go.
hoping that a property name would magically be inferred from person
let result = { message.person, tag: 1 };
There is the ECMAScript Shorthand Property Assignment Improvements proposal that would allow exactly this. Unfortunately, it's still at stage 0 :-/
Assuming you're using ES6:
const message = {
person: {
name: 'John'
},
unwanted: 'x'
};
let { unwanted, ...result } = { ...message, tag: 1 };
console.log(result);
If you'd like to rename the unwanted variable to something then to dit
let { unwanted: dummy, ...result } = { ...message, tag: 1 };
These have some issues, e.g
This creates an extra variable unwanted (or dummy if you use the 2nd approach)
If you have multiple unwanted properties, you have to write them all in the destructuring
So in my opinion, you're better off to go with the way you described in your question already.
I'm reading a Nodejs book and I came accross to this syntax below
function createClient (ns, opts) {
return createClient[ns] || (createClient[ns] = nos(net.connect(opts)))
}
Didn't quite understand the createClient[ns] = nos(net.connect(opts)) part, What it means? Why should I use it?
Is this syntax documented anywhere?
This is taking advantage of the ability to chain the assignment operator to assign values to multiple variables at once. Stated another way, the return value of an assignment expression is the value that was assigned.
The simplest demonstration is to the log the output of an assignment operation:
console.log(test = 'foo');
This behavior is mentioned in the MDN docs for the assignment operator:
Chaining the assignment operator is possible in order to assign a
single value to multiple variables
In your code snippet, the intention seems to be "return the value of createClient[ns] unless it's false (e.g. unassigned), otherwise assign the value of nos(net.connect(opts)) to createClient[ns] and return that value."
This is a simple form of caching and implies that nos() is an expensive operation that shouldn't be repeated unless necessary.
Here's a simplified example:
let values = {};
function assignIfNotSet(arg) {
return values['example'] || (values['example'] = arg);
}
console.log('before:');
console.log(values);
let result = assignIfNotSet('foo');
console.log('after:');
console.log(values);
console.log('result: ' + result);
In ESLint, the no-param-reassign rule as documented here, forbids you from assigning the value of a function param.
This is to avoid having a function's arguments object be modified.
The correct way to code is to reassign the param to a local var and return the var. This is fine for some types, but it seems pointless for objects passed to a function.
For example, let's take this function;
function foo(param) {
var copy = param; // This makes the linter happy
copy.bar = 2;
console.log('arg 0: ', arguments[0], 'param:', param, 'copy:', copy);
return copy; // A pointless return, the original object has been modified.
}
let test = { bar: 1 };
foo(test);
console.log(test); // Has been modified
test = foo(test); // a pointless reassignment, foo has already changed test.
console.log(test); // Same effect as previous function call.
To be fair, ESLint does allow you to turn this feature off with /*eslint no-param-reassign: ["error", { "props": false }]*/; but I have to wonder why?
The point of this rule is to get rid of mutability and keep the arguments object pure, but a simple reassignment of an object will not do this.
The only way to truly do that would be to deep clone the param and assign that to a function scoped variable.
Am I missing something here?
The likely reason that it doesn't warn when you assign the parameter to a variable is that it would require complex data flow analysis. Suppose you have code like this:
function foo(param, flag) {
var copy = flag ? param : {...param};
copy.bar = 2;
console.log('arg 0: ', arguments[0], 'param:', param, 'copy:', copy);
return copy; // A pointless return, the original object has been modified.
}
Now it can't tell whether copy contains the same object as param or a clone, it depends on the value of flag.
Or something like this:
function foo(param) {
var copy = param;
var copy2 = copy;
var copy3 = copy2;
copy3.bar = 2;
console.log('arg 0: ', arguments[0], 'param:', param, 'copy:', copy3);
return copy3;
}
This would require keeping track of the entire chain of references to determine that copy3 is the same as param.
Tracking this isn't impossible, optimizing compilers often do it. But it may be overkill for a linter.
Barmar's answer really hits the point. I am still adding my two cents here because this rule is very important when it comes to large codebases. Starting with your example only, the job of ESLint is to point the wrong coding practices. We, as developers, can still fool the linter though in many ways!
The correct way to address this ESLint error in function foo would have been this -
function foo(param) {
// This makes the linter happy, as well as the codebase :')
const copy = { ...param, bar: 2 };
console.log('arg 0: ', arguments[0], 'param:', param, 'copy:', copy);
return copy; // No modifications to the original object
}
As you can see, deep clone of the param is not required.
Regarding your question about the why of this rule, have a look at this super-long thread running on Github discussing the very same question that you have mentioned - https://github.com/airbnb/javascript/issues/719. Hope it helps you learn something new and interesting :)
Side Note - Simply reassigning parameters also makes code hard to follow. It also seems a bad idea as it deoptimizes in many engines, specifically v8. To be honest, I am still reading more on this to understand this last line better. In case you want to read on this too, see here - https://github.com/airbnb/javascript/issues/641#issuecomment-167827978
I have a program that is incrementing requests on a session cookie and printing them out to the console. Initially, I was trying to figure out how I could save this data. After logging in a couple places, I realized that the data was being saved/changed despite me having a seperate variable to hold what I thought was a temporary version of the req member object.
This is the code that made me realize that the actual object was being changes when I incremented the variable I assigned it to:
recordRequest(req) {
const { ip } = req.info;
const { requestsPerSecond } = req.session;
if (req.originalUrl.split('/').filter(Boolean)[0] == 'www.example.com') {
requestsPerSecond[ip] = requestsPerSecond[ip] + 1 || 1;
}
console.log(req.session.requestsPerSecond);
}
I can't seem to find in the docs here or on Mozilla whether or not this is intended behavior, whether or not this is a result of my use of const (where you can mutate member variables), or there is some kind of weird bug going on. I also had trouble reproducing this example on a smaller scale, but I verified that nothing going in or going out of the function is affecting this chunk of code.
It's not breaking my code or anything (it's actually making my life easier) but I want to understand why this is happening!
I would default to object destructuring working essentially the same as normal assignments. Consider:
const req = {session: {requestsPerSecond: {"0.0.0.0": "foo"}}};
const requestsPerSecond = req.session.requestsPerSecond;
// updates to `requestsPerSecond` will also update `req`.
I'm not sure you can use destructuring to break the assignment, so you will have to use normal tactics:
const requestsPerSecond = Object.assign({}, req.session.requestsPerSecond);
From MDN:
The destructuring assignment syntax is a JavaScript expression that makes it possible to extract data from arrays or objects into distinct variables.
If this data happens to be an object reference, this object reference will be copied into the new variable, or in your case constant.
Minimal example:
const orig = {
foo: {
bar: 1
}
}
const { foo } = orig;
console.log(foo.bar); // 1
console.log(orig.foo.bar); // 1
foo.bar++;
console.log(foo.bar); // 2
console.log(orig.foo.bar); // 2