TS2532: Object is possibly 'undefined'. on array.map() - javascript

I understand that this TS error is essentially just a warning but I have been unable to find a solution when it occurs on .map
const files = require.context("./", true, /\.vue$/i);
files.keys().map(key =>
Vue.component(
key
.split("/")
.pop()
.split(".")[0],
files(key).default
)
);
I have tried checking if the value of key exists before doing anything else but it still produces the same error.
TS2532: Object is possibly 'undefined'.

You are trying to split a string. Using someString.split("/"), which does return an array. Using the method pop() does either return the last element of the array or undefined (according to MDN)
Therefore your typing at that point is: string | undefined executing .split(..) on an undefined value will cause problems. That's what TypeScript is trying to tell you here.
To avoid this warning/error and to be type safe you could use the latest optional chaining feature TypeScript 3.7.0 provides you if applicable:
key.split("/").pop()?.split(".")[0] ?? someDefaultString
An alternative solution would be to extract this kind of logic into another function like so:
function extractValue(key: string): string { // you want to name that function better though
return key.split("/").pop()?.split(".")[0] ?? "defaultValue";
}
And use it like:
Vue.component(extractValue(key), files(key).default)

Related

Is it possible to let the interpreter know my method checks for undefined/null in TypeScript

For example I have a method isEmpty that checks if anything is empty, null, or undefined and returns true if so.
However in TypeScript it doesn't let the interpreter know that this is the case and I get a red underline in my IDE (WebStorm)
Example code
let str: string | undefined = undefined
if (!isEmpty(str)) {
doSomeWorkFunction(str) // this line is shows the error 'string | undefined is not assignable to type string
}
However if the code is
let str: string | undefined = undefined
if (str) {
doSomeWorkFunction(str) // no error because the interpreter knows I checked the value
}
The fix I would like to avoid is
let str: string | undefined = undefined
if (!isEmpty(str)){
// #ts-ignore
doSomeWorkFunction(str) // no error since ts is now ignoring this error
}
How might I go about still keeping the TypeScript strict null checks in place without having to ignore issues like this.
TypeScript has a feature called "type guards" that helps in this situation: https://www.typescriptlang.org/docs/handbook/advanced-types.html. Specifically, it lets you tell the compiler that the return type is not just a boolean, but a boolean that means something specific about the types of the inputs. For example, you can convert a function like this
function isDefinedString(input: string | undefined): boolean
into a function like this:
function isDefinedString(input: string | undefined): input is string
The return type is still a boolean, but now the compiler will assume that the input is specifically a string and not any other type allowed by the argument declaration (in this case undefined).
Try using this signature on your existing isEmpty function declaration. Although not required to make it work, because you are adding this additional context to the function signature I'd recommend changing the name of isEmpty to reflect its dual purpose of checking emptiness and whether the variable is defined.
Edit:
One caveat to returning type information is that returning false will make the compiler assume that the object is not that type. In the above example, if isDefinedString returns false then the compiler will assume that it is not a string. This runs into problems with any or generic parameters, because returning false effectively tells the compiler that there is no type (or in the compiler's words, there is "never" a type) that satisfies your criteria. While this doesn't result in an error directly, the fact that the compiler has no type that works with your object means you can't do anything meaningful with the object in the if/else branch triggered by your type guard returning false. As such, if you are using a broad type such as any or a generic, you will want to limit what your type guard says to something like input is (null | undefined) or input is MySpecificInterface if you plan to do something meaningful in both true and false cases. This trickiness may also be a sign that you want to separate your validation into two checks:
if(typeGuard(myObject)) {
if(isValid(myObject)) {
// do something with valid object
} else {
// do something with invalid object
}
}
// do nothing without an object to act upon

How to test this statement using jest and enzyme

In our code, I have two statements
const { column, showTooltip, tooltipValue, data } = props;
const key = column.bindProperties[0].properties[0].name;
on testing, this gives error as
"TypeError: Cannot read property '0' of undefined."
what is the meaning of this statement column.bindProperties[0].properties[0].name; and how to test it.
In JS you can't guarantee that objects have certain properties.
When you try to access column.bindProperties[0].properties[0].name, either column.bindProperties or column.bindProperties[0].properties is undefined - hence the error you're getting.
You can either use lodash's _.get() or validate the keys are defined using the redundantly annoying:
const key = column
&& column.bindProperties
&& column.bindProperties[0]
&& column.bindProperties[0].properties
&& column.bindProperties[0].properties[0]
&& column.bindProperties[0].properties[0].name;
This will make sure your code won't break. If one expression in the chain isn't defined, the expression will stop evaluating and you'll just get undefined as the result.
Since no one has really just spelt it out, here's an example of optional chaining:
const key = column?.bindProperties?.[0]?.properties?.[0]?.name;
and with nullish coalescing:
const key = column?.bindProperties?.[0]?.properties?.[0]?.name ?? "I'm a fallback value";

How can I use optional chaining with arrays and functions?

I'm trying to use optional chaining with an array instead of an object but not sure how to do that:
Here's what I'm trying to do myArray.filter(x => x.testKey === myTestKey)?[0].
Also trying similar thing with a function:
let x = {a: () => {}, b: null}
console.log(x?b());
But it's giving a similar error - how can I use optional chaining with an array or a function?
You need to put a . after the ? to use optional chaining:
myArray.filter(x => x.testKey === myTestKey)?.[0]
Playground link
Using just the ? alone makes the compiler think you're trying to use the conditional operator (and then it throws an error since it doesn't see a : later)
Optional chaining isn't just a TypeScript thing - it is a finished proposal in plain JavaScript too.
It can be used with bracket notation like above, but it can also be used with dot notation property access:
const obj = {
prop2: {
nested2: 'val2'
}
};
console.log(
obj.prop1?.nested1,
obj.prop2?.nested2
);
And with function calls:
const obj = {
fn2: () => console.log('fn2 running')
};
obj.fn1?.();
obj.fn2?.();
Just found it after a little searching on the what's new page on official documentation
The right way to do it with array is to add . after ?
so it'll be like
myArray.filter(x => x.testKey === myTestKey)?.[0]
I'll like to throw some more light on what exactly happens with my above question case.
myArray.filter(x => x.testKey === myTestKey)?[0]
Transpiles to
const result = myArray.filter(x => x.testKey === myTestKey) ? [0] : ;
Due to which it throws the error since there's something missing after : and you probably don't want your code to be transpilled to this.
Thanks to Certain Performance's answer I learned new things about typescript especially the tool https://www.typescriptlang.org/play/index.html .
ECMA 262 (2020) which I am testing on Edge Chromium 84 can execute the Optional Chaining operator without TypeScript transpiler:
// All result are undefined
const a = {};
console.log(a?.b);
console.log(a?.["b-foo-1"]);
console.log(a?.b?.());
// Note that the following statements throw exceptions:
a?.(); // TypeError: a is not a function
a?.b(); // TypeError: a?.b is not a function
CanIUse: Chrome 80+, Firefox 74+
After a bit of searching the new page in the official documentation, it was discovered.
You need to put a . after the ? to use optional chaining.
So it will be so,
myArray.filter(x => x.testKey === myTestKey)?.[0]
Used only ? Makes the compiler think that you are trying to use a conditional operator (then it causes an error because it doesn't see a : later)
It's not necessary that the function is inside the object, you can run a function using optional chaining also like this:
someFunction?.();
If someFunction exists it will run, otherwise it will skip the execution and it will not error.
This technique actually is very useful especially if you work with reusable components and some components might not have this function.
Well, even though we figured out the correct syntax, the code doesn't make much sense to me.
The optional chaining in the code above is making sure, that the result of myArray.filter(x => x.testKey === myTestKey) is not null and not undefined (you can have a look at the TS output). But it is not possible anyway, because the result of the filter method is always an array. Since JavaScript doesn't throw "Array bounds exceeded", you are always safe when you try to access any index - you will get undefined if this element doesn't exist.
More example to make it clear:
const myArray: string[] = undefined
console.log(myArray.filter(x => x)?.[0]) //throws Cannot read property 'filter' of undefined
//in this example the optional chaining protects us from undefined array
const myArray: string[] = undefined
console.log(myArray?.filter(x => x)[0]) //outputs "undefined"

Why use _.get(err, ['data', 'error', 'code']) if you could simply do err.data.error.code?

So I've been looking for an answer given to me in a nodeJS test.
Why use _.get(err, ['data', 'error', 'code']) if you could simply do err.data.error.code?
And can you think of a better name for err here?
after 3 days of searching i can't figure out an answer ,I may need some help
Code:
if (_.includes(errorCodesOnWhichToRemoveSynchronization,
_.get(err, ['data', 'error', 'code']))) {
// ...
}
Update
A preferred way to do this without any external library is to leverage optional chaining.
err?.data?.error?.code
Lodash's _.get is failsafe. If any of the intermediate property doesn't exist, it simply returns undefined, instead of throwing an error.
See the demo below:
var obj = {
data: {
}
};
try {
console.log(obj.data.error.code);
} catch(e) {
console.log('regular method throws error');
}
console.log('lodash returns', _.get(obj, ['data', 'error', 'code']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
By the way, in your case, ['data', 'error', 'code'] can simply be data.error.code.
Imagine err.data is undefined or an empty object. In that case, you would get an exception that js cannot read error from undefined. Using underscore/lodash's get function is a "safer" way to access it. It does not throw that exception and just returns undefined.
Optional Chaining (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
err?.data?.error?.code
Lodash has few methods to access properties that are quite handy and _.get is one of them where the main idea is for you to not have to worry if the path is not valid due to a falsy value along the way. Not only that but it has a defaultValue 3rd parameter (different in lodash/fp) where you could set a default value if the path asked for is invalid:
_.get(object, 'a.b.c', 'foo'); // if a.b.c is falsy you would get 'foo'
_.has is another one which would return a boolean if the requested path exists or not.
_.result is super handy when it comes to getting a value from a path which goes trough functions. Think about knockout and observables for example where to get from a to b you would usually need to do a().b().c().myValue ... so with _.result you would do:
_.result(obj, 'a.b.c.myValue')

How to convert anything to a String safely in JavaScript

If I have:
var test = {toString: function(){alert("evil code"); return "test";}};
how can I convert test to a string? without calling test.toString() and without using a typeof x == "string" check since I want to allow non strings.
Note: this is for a FF extension dealing with objects from a content page's js scope.
Sadly, #Matthew Flaschen's (currently accepted) answer does not work with the Symbol class from ES6 / ES2015:
console.log("" + Symbol("foo"));
// results in an exception:
// `Uncaught TypeError: Cannot convert a Symbol value to a string`
// (at least in Chrome as of this writing).
https://jsfiddle.net/L8adq9y4/
(I have no idea why, as Symbol has a perfectly good toString() method:)
console.log(Symbol("foo").toString());
https://jsfiddle.net/v1rqfhru/
There's a solution though: the String() function seems to be able to convert any value (at least out of the ones I tried) into a String. It will even call toString() if it exists:
console.log(String("A String"));
console.log(String(undefined));
console.log(String(null));
console.log(String({value: "An arbitrary object"}));
console.log(String({toString: function(){return "An object with a toString() method";}}));
console.log(String(function(){return "An arbitrary function"}));
https://jsfiddle.net/6uc83tsc/
So, pass anything you like into String() and you'll get a pretty good result.
JavaScript allows you to modify the properties of pretty much any object that is accessible to your script, including Object.prototype itself, meaning any object is vulnerable to "evil code" in the manner that you explained.
Only primitives are guaranteed to be safe, so the only way to ensure that "evil code" is never executed is to do something like this:
function safeToString(x) {
switch (typeof x) {
case 'object':
return 'object';
case 'function':
return 'function';
default:
return x + '';
}
}
One option is:
Object.prototype.toString.call(test)
This gives:
"[object Object]"
in the case of test. Basically, it just gives type information. However, I wonder what the exact scenario is here. How is the evil object getting loaded into the page? If they can execute arbitrary code on the page, you're basically out of luck. Among other things, it is then possible to redefine Object.prototype.toString.
Your (toString: function(){alert("evil code"); return "test";}) doesn't even get parsed here, it throws a syntax error. I think you wanted to use {} instead of ().
Normally you could use an empty string and the plus operator to perform a cast:
""+test;
""+2; // "2"
""+4.5 // "4.5"
""+[1, 2, 3] // "1,2,3"
""+{} // '[object Object]'
But here, there's no real way to convert the object safely.
You can use delete test.toString to get rid of the overridden method, after that it will fall back to the normal toString method which returns '[object Object]'. You can also convert the toString method itself into a string via test.toString.toString().
"function () { alert("evil code"); return "test"; }"
It's up to you what you exactly want to do here.
You can check only undefined cases and convert it to using String constructor
like this.
let a = [1,2,3]
String(a)
Exceptional case : String(undefined) --> "undefined"
Hope it helps
You can use lodash toString method.
_.toString(null);
// => ''
_.toString(-0);
// => '-0'
_.toString([1, 2, 3]);
// => '1,2,3'
Link to documentation
In JavaScript the function String is Janus-faced.
If called as a constructor it will create an object.
If called as a function if will coerces every value into a primitive string.
console.log(typeof new String())
console.log(typeof String())
So just use String(anything) without new.

Categories

Resources