ES2016 default parameter as empty array causes "no-empty-pattern" warning - javascript

I am in the process of building a React app which I have used Create React App as the build-environment. Within one of my (class-based) components I have the following line within the componentDidUpdate() method:
const toast = ([] = this.props.toast.toast);
If my understanding is correct, this is ES2016 syntax for setting default values if a variable is undefined. (Please let me know if this is not correct, although it's not the primary point of this question).
When my React app renders, I get the following warning in the console: Unexpected empty array pattern no-empty-pattern.
I understand that ESLint "moaning" about the fact it's an empty array. But as I want an empty array in that situation, why does this cause a warning?
Can I simply tell ESLint to ignore it (using // eslint-disable-line no-empty-pattern)?

That is not doing what you think it's doing at all. It's not setting anything to an empty array, nor is it a syntax for default variables. What it's doing is abusing array destructuring syntax without benefit. Try it yourself: if you execute this code, the value of toast will be undefined:
const toast = ([] = undefined);
There is no ES6 syntax for default variables, as far as I know, other than default function parameters, which is quite different from what you're using. If you want default variable values, just use the tried-and-true method of short-circuiting:
const toast = this.props.toast.toast || [];
To elaborate, proper array destructuring works something like this (assuming this.props.toast.toast is an array):
const [first, second, third] = this.props.toast.toast;
first === this.props.toast.toast[0]; // true
second === this.props.toast.toast[1]; // true
third === this.props.toast.toast[2]; // true

Related

How to use enum-like javascript-class that assigns its values without constructor

I have this class, which is supposed to represent an enum:
export default class ChangeStatusEnum {
"added" = "added";
"deleted" = "deleted";
"edited" = "edited";
"unedited" = "unedited";
static constructFromObject(object) {
return object;
}
}
It is generated in the pipeline by openapi-generator1 so changing it is not an option. This is not a question on best practices for defining enums in vanilla-js or typescript, this question is about how to use this class.
I do not understand the syntax of assigning to a string, I do not know where these four strings are accessible.
Here are a few things that I have tried (in a jenkins-test so they can be run easily):
test("access", () => {
console.log(ChangeStatusEnum) // prints [class ChangeStatusEnum]
console.log(JSON.stringify(ChangeStatusEnum)) // prints undefined
console.log(
ChangeStatusEnum.constructFromObject("deleted") === "deleted"
) // prints true
console.log(
ChangeStatusEnum.constructFromObject("nonexisting") === "nonexisting"
) // also prints true, which means this syntax has no value over just using strings instead of enums
console.log(ChangeStatusEnum["added"]) // prints undefined
console.log(ChangeStatusEnum.added) // prints undefined
})
The least I expect from a datastructure that calls itself "enum" is that I can construct and compare values of it without fear of silently constructing non-existing values. Iterating over all values of an enum would also be nice, but is not strictly necessary.
I suppose there is a way to do that with this datastructure that I just do not know of due to lack of knowledge of advanced javascript-constructs.
1 The tool is openapi-generator-cli in version 2.5.1 https://www.npmjs.com/package/#openapitools/openapi-generator-cli with openapi-generator-maven-plugin version 6.0.0 https://mvnrepository.com/artifact/org.openapitools/openapi-generator-maven-plugin
Since this is somewhat mature tooling I expect their enum-solution to be usable, this is why I ask this question as a js-question and not as an openapi-question.
I think the most elegant way to use it is to create an instance of it and use it as a constant. The 'constructFromObject'-function is not needed for this. So just put this below the imports:
const changeStatusEnum = new ChangeStatusEnum();
Afterwards, the members can be accessed using normal dot notation:
changeStatusEnum.added // evaluates to the string "added"

JS optional chaining clarification

I found a really great use case for optional chaining in my react project. I have used it slightly before but this line of code has made me a bit confused.
I have a table that is sortable.
One of the columns shows success or failure depending on if failed_date is null or a date.
sortedRows = sortedRows.sort((a, b) => a?.failed_date?.localeCompare(b?.failed_date));
But What confuses me is which value does the '?.' check is nullish?
Does a.failed_date?.localeCompare() check if failed_date?. is null/undefined or does it check if ?.localeCompare() is null/undefined?
Same with b?.failed_date is it checking b to be nullish? or failed_date.
I think My confusion comes from looking at the Documentation
Because arr?.[50] checks if element 50 is nullish but obj.method?.() checks if the method is not nullish?
Looking at this second block of code (which I believe is now correct) a.failed_date may be nullish and won't do localeCompare if a.failed_date is null?
But what if a.failed_date is not null but b.failed_date is null?
Does localeCompare not care? I haven't gotten an error but I was using localeComapre(b?.failed_date)
sortedRows = sortedRows.sort((a, b) => a.failed_date?.localeCompare(b.failed_date));
Let's say you define variable like below
const variable = { value: 'test' };
then you want to access variable?.value it equals variable === null || variable === undefined ? undefined : variable.value.
Same with array.
Check typescript playground and see js output https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhgJwXAnjAvDA2gXQNwBQBiyKA-AHRYCsADDkA
Basically, the ? in that context means optional chaining.
How it works is, for example, if you define an object like below, and you want to try and access the views property, it will throw an error.
const obj = { website: "stackoverflow.com", };
console.log(obj.views.toFixed(0)); // Error!
This is because you are trying to access a method off of undefined, which doesn't have anything.
To avoid this error, we can use optional chaining, like below.
const obj = { website: "stackoverflow.com", };
console.log(obj?.views?.toFixed(0)); // undefined
Optional chaining tells JavaScript to complete the method/read the key, but if it doesn't exist, instead of throwing an error, just return undefined.
It also works with arrays; the same way! If, say, index 78 doesn't exist, instead of throwing an error, it will just return undefined.
const arr = [1, 2, 3];
console.log(arr?.[78]?.toString());
To elaborate here, it is possible to stack multiple optional chaining operators as seen in OP's code a?.failed_date?.localeCompare(b?.failed_date)
In these cases, it is not a question of which object key value will be checked. The code will be evaluated from left to right and if any of the object values are nullish then undefined will be returned.
Refer to the documentation for further understanding
MDN Optional Chaining

ES6 - Variable being defined inside closure with a name same as a variable in parent scope yields undefined

I spent hours looking at this piece of code:
1: const { services, lists } = this.props;
2:
3: const List = _.map(lists.list, (list) => {
4: const services = _.filter(services.list, (service) => lists.services.indexOf(service.id) > -1);
5: ...
120: ...
121: });
At line number 4, I get an error:
Cannot read property list of undefined
At line number 4, property list is being referenced by services, apparently. And, it is undefined. But, at line 1, it's defined which can be checked by logging its value.
I know the value of services becomes undefined in the closure because JS has its way of finding the variable declarations first and assigning undefined to them before executing the given function. My concern is that, should it be the behavior with the given piece of code considering it's being pre-compiled using Babel?
Edit: I realized that this was your actual question:
My concern is that, should it be the behavior with the given piece of code considering it's being pre-compiled using Babel?
The answer is no, Babel's job is to convert code into its older equivalent, the equivalent here being a var statement. The const statement you have there is perfectly valid syntactically, but is destined to throw an error semantically (note: if you actually executed that const statement in an environment that supports const, it would throw an error before you even tried to access service's list property).
Original answer:
If you define a variable in the same line that you use it, it will always be undefined the first time the statement tries to access it, even if there's a variable with the same name in an outer scope:
const a = 2;
function f() {
const a = a + 1; // error
console.log(a);
}
f();
The solution: don't reuse variable names in inner scopes. Just come up with new names for your variables:
const filteredServices = _.filter(services.list, (service) => lists.services.indexOf(service.id) > -1);

Are any aspects of object destructuring assignments by reference?

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

Object literal multiple assignment in CoffeeScript

I'm a bit of a newbie in Javascript. I was looking through a bit of Coffeescript code for an Atom package, and I stumbled upon this piece of code:
loadProperties: ->
#properties = {}
fs.readFile path.resolve(__dirname, '..', 'completions.json'), (error, content) =>
{#pseudoSelectors, #properties, #tags} = JSON.parse(content) unless error?
return
I was a bit confused by the last line {#pseudoSelectors, #properties, #tags} = JSON.parse(content) unless error? because it seems like it assigns multiple values from the parsed JSON content. In my confusion, I decided to convert this back to Javascript using js2Coffee, and I ended up with the following:
function() {
this.properties = {}; // make list of properties (global to provider)
return fs.readFile(path.resolve(__dirname, '..', 'completions.json'), (function(_this) { //load completions.json (using path module)
return function(error, content) { // edit: nvm, js2coffee's fault. not sure why they wrapped the call back in another anonymous function, but this is a node stream callback
var ref;
if (error == null) { // if there are no errors
ref = JSON.parse(content), _this.pseudoSelectors = ref.pseudoSelectors, _this.properties = ref.properties, _this.tags = ref.tags;
}
};
})(this));
This code is a bit more understandable than the above. I can see that ref is assigned the object parsed from the content stream, and is then used to assign the other variables with their designated data. My question is, how does this type of assignment work? In Coffeescript, how does the preprocessor know where to assign the values, and in what order to assign them in?
By inspecting completions.json, the data is not in the order in which the assignments occur.
This is known as Destructuring Assignment.
To make extracting values from complex arrays and objects more convenient, CoffeeScript implements ECMAScript Harmony's proposed destructuring assignment syntax. When you assign an array or object literal to a value, CoffeeScript breaks up and matches both sides against each other, assigning the values on the right to the variables on the left.
CoffeeScript interprets an object or array on the left side of an = as a pattern, matching the names used...
#pseudoSelectors
#properties
#tags
...to properties or indices within the value being assigned:
JSON.parse(content).pseudoSelectors
JSON.parse(content).properties
JSON.parse(content).tags
(Defining the additional ref to avoid reevaluating JSON.parse(content) for each.)
As for order, CoffeeScript will generally use the order they're mentioned within the assignment. Moving #pseudoSelectors to the 3rd property in the pattern will be echoed in the generated JavaScript.
{#properties, #tags, #pseudoSelectors} = JSON.parse(content) unless error?
var ref;
if (typeof error === "undefined" || error === null) {
ref = JSON.parse(content),
this.properties = ref.properties,
this.tags = ref.tags,
this.pseudoSelectors = ref.pseudoSelectors; // now last
}
Though, JavaScript Objects, like the result of JSON.parse(content), aren't enforced as sorted data structures. If you need to ensure the order of the values, you'll have to instead use an Array.

Categories