I'm having trouble getting TypeScript to recognise that an optional property of an object is defined.
type OuterKeys = 'a' | 'b';
type Inner = {
value: '';
}
type Outer = Partial<Record<OuterKeys, Inner>>;
const thisGivesError = (key: OuterKeys, outer: Outer) => {
if (outer[key]) {
console.log(outer[key].value);
}
}
Even though I explicitly check if outer[key] is defined, I'm getting error when trying to access outer[key].value: Object is possibly 'undefined'.
How can I work around it? The only way I found is assigning outer[key] to a variable and having another if, like below. Is this the only way to go?
const thisWorks = (key: OuterKeys, outer: Outer) => {
const o = outer[key];
if (o) {
console.log(o.value);
}
}
The playground with the code can be found here
Edit: I don't want to use ! operator, as it feels like a workaround - why would I tell TypeScript that this value is set using non-null assertion, if I already checked for its existence with if? Same for .? - I already have the if, so this value for sure exists and is not optional.
That's #10530, currently TS doesn't narrow down on indexed access.
Because the inner object which holds the value is never specified to not be undefined.
You can fix this by simply adding a question mark.
Ex adding a question mark:
const thisGivesError = (key: OuterKeys, outer: Outer) => {
if (outer[key]) {
console.log(outer[key]?.value);
}
}
At this point, after checking for outer[key] being truthy, it's safe to go with non-null assertion:
if (outer[key]) {
console.log(outer[key]!.value);
}
Though I don't know how to make TypeScript figure it out on its own.
You can make use of object?.key operator. You can learn more about it here.
Your final code will be:
const thisWorks = (key: OuterKeys, outer: Outer) => {
if (outer[key]) {
console.log(outer[key]?.value);
}
}
Related
I'm new with Typescript and I have a problem with using a variable to choose the property of an object.
I have a function that filters an array on a property passed as a parameter. The function receives 3 parameters: an array of objects, the characters sought and the property that will be filtered.
here is the function.
type Data = {
[key: string]: string | number;
};
function filtreTexte(arr: Data[], searchCar: string, field: string): Data[] {
return arr.filter((el) => {
if (el[field] === "string") {
return el[field].toLowerCase().indexOf(searchCar.toLowerCase()) !== -1;
}
})
}
The problem on the .toLowerCase() which tells me that the method does not work on a number|string type even though I am testing if it is indeed a string.
If I change the el[field] to el.name, the error is no longer mentioned.
How should I do it ?
Thank you in advance for your help.
Your check for the type needs to look like this: if (typeof el[field] === "string") rather than if (el[field] === "string") - the latter case will only be true if the value of el[field] is exactly 'string'.
What #Dakeyras said is correct. You should add the typeof operator.
But the narrowing will still not work. Even though we checked if the type of el[field] is string, the compiler will inconveniently forget this fact when we try to use it next. Generally the compiler would not try to narrow the type in this scenario where el has an index signature and field is just some string. A lot of things could break if that would happen. For example, field could have changed between the check and later usage. For a further discussion on all the different limitations of TypeScript's Control Flow Analysis, see this.
To make this work, assign el[field] to an extra variable val, perform the narrowing on val and then use val later. Also don't forget to add another return statement to make the "Not all code paths return a value." error disappear.
function filtreTexte(arr: Data[], searchCar: string, field: string): Data[] {
return arr.filter((el) => {
const val = el[field]
if (typeof val === "string") {
return val.toLowerCase().indexOf(searchCar.toLowerCase()) !== -1;
}
return false
});
}
Playground
You need to wrap the el[field] into an variable declaration, this offers more specific info about the variable type since was declard before, el[field] is dynamic value wich can be string or number, so this will thrown an error.
const element = el[field];
if (typeof element === 'string') {
return element.toLowerCase().indexOf(searchCar.toLowerCase()) !== -1;
}
Also, it works with el.name because is a specific property, the same way as the above example.
Hope it helps.
I am reading the Typescript Handbook and, right now, I am currently stuck at Call Signatures subsection. In the example given:
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}
I cannot figure out in TS Playground how to invoke the doSomething function. I tried the below but it is not working.
doSomething({ description: "The code", (5): false})
A DescribableFunction is first and foremost a function that takes a single number input and returns a boolean. It also has a string-valued description property.
Since TypeScript 3.1, and as implemented in microsoft/TypeScript#26368, you've been allowed to add properties to functions after their declarations without running into compiler warnings. So you can make a DescribableFunction relatively straightfowardly.
Here's how you could do it with a function statement:
function greaterThanTen(someArg: number) {
return someArg > 10;
}
greaterThanTen.description = "greaterThanTen";
doSomething(greaterThanTen); // "greaterThanTen returned false"
And here's how you could do it with a function expression:
const isEven: DescribableFunction = someArg => someArg % 2 === 0;
isEven.description = "isEven"
doSomething(isEven); // "isEven returned true"
If you want a one-liner, you could use Object.assign() to add properties to a target and return the augmented target, which TypeScript represents as the intersection of the function type and the added property objects. (This worked even before TypeScript 3.1.) Observe:
const isNegative = Object.assign(
(someArg: number) => someArg < 0,
{ description: "isNegative" }
);
doSomething(isNegative); // "isNegative returned false"
Playground link to code
Functions with additional properties seem unergonomic; at least you cannot declare them using a single plain object.
This is one way:
const fn = ((x: number) => false) as DescribableFunction;
fn.description = 'description';
doSomething(fn);
I feel like the answer to this is a hard no in most languages I've used other than PHP, which at least used to have some odd corner cases with stuff like $someArray['nonexistentKey']++.
I'm basically looking to write a sparse object where each property is a numeric counter. The counters should return 0 if undefined and should automatically define themselves with a value of 0 if you try to increment them.
In other words, I want to override the generic Object getter to return 0 in all cases where it would return undefined... or perhaps define the property right there during the get and set it to 0.
So in theory, an overload for ANY property not yet defined would get initialize it at zero. For example this:
myObj['hugePrimeNumberToBaseXToString']++;
would then make it 1.
In the olden days I feel like some way with Object.__proto__ might have worked for this case...
I think what you want is a Proxy.
You can use a proxy to intercept property gets and return your own logic. In this case, return zero in the case of an undefined property.
// Hold the data
const target: { [key in string]: number } = {}
// Access the data
const proxy = new Proxy(target, {
get(target, prop, receiver) {
if (typeof prop === 'string') {
if (target[prop] === undefined) return 0
return target[prop]
}
return undefined
}
})
proxy['hugePrimeNumberToBaseXToString']++
console.log(proxy['hugePrimeNumberToBaseXToString']) //=> 1
Playground
Proxy is definitely the right answer, but I'd argue that tinkering with Object directly is much farther under the hood than you want to be.
Instead, I'd make a new, simple object type like so:
const Dictionary = function() {}
Dictionary.prototype.add = function(key) {
if (this.hasOwnProperty(key)) {
this[key] += 1;
} else {
this[key] = 1;
}
}
const dict = new Dictionary();
dict.add("apples");
console.log(dict.apples) // 1
dict.add("apples");
dict.add("bananas");
console.log(dict.apples, dict.bananas) // 2, 1
It's not quite what you wanted since you have to call add each time, but I'd take the three-character tradeoff for the sake of simplicity and extensibility.
Codepen
I am trying to map the repetition of letters into a hashmap and then returning the first non-repeated character in a string. Following is the function I've written to do so:
export const firstNonRepeatedFinder = (aString: string): string | null => {
const lettersMap: Map<string, number> = new Map<string, number>();
for (let letter of aString) {
if (!lettersMap.has(letter)) {
lettersMap.set(letter, 1);
} else {
incrementLetterCount(letter, lettersMap);
}
}
for (let letter of aString) {
if (lettersMap.get(letter) === 1) return letter;
}
return null;
function incrementLetterCount(
aLetter: string,
aLettersHashMap: Map<string, number>
): void {
if (
aLettersHashMap.has(aLetter) &&
aLettersHashMap.get(aLetter) !== undefined
) {
aLettersHashMap.set(aLetter, aLettersHashMap.get(aLetter) + 1);
}
}
};
However, in incrementLetterCount, function, although I am handling to exclude the undefined values for getting a key in the hashmap, it still complaining about Object is possibly 'undefined' which means that the get method is might return undefined and I cannot proceed with it.
Does anyone know what I am missing here that results in this error?
I found the following solution to my problem but still not sure why the original code snippet was throwing a compile-time error (although the code was checking against undefined values).
It seems that in Typescript we can assert that the operand is not null/undefined (from here).
A new ! post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operation x! produces a value of the type of x with null and undefined excluded.
function incrementLetterCount(
aLetter: string,
aLettersHashMap: Map<string, number>
): void {
aLettersHashMap.set(aLetter, aLettersHashMap.get(aLetter)! + 1);
}
Another reason is the tsconfig.json might have the following settings
"strict": true, /* Enable all strict type-checking options. */
if you take out the line, the error should go away.
I've taken the code from David Walsh's css animation callback and modified it to TypeScript. However, I'm getting an error and I don't know why:
interface IBrowserPrefix {
[key: string]: string;
}
// http://davidwalsh.name/css-animation-callback
function whichAnimationEvent() {
let x: keyof IBrowserPrefix;
const el = document.createElement('temp');
const browserPrefix: IBrowserPrefix = {
animation: 'animationend',
OAnimation: 'oAnimationEnd',
MozAnimation: 'animationend',
WebkitAnimation: 'webkitAnimationEnd',
};
for (x in browserPrefix) {
if (el.style[x] !== undefined) {
// ^---- [TS Error]: Element has 'any' type b/c index expression is not of type 'number'
return browserPrefix[x];
}
}
}
This is happening because you're attempting to index an object with a numeric index signature with string keys.
for x in browserPrefix will give you back a set of keys, which are strings. However for some reason CSSStyleDeclaration has its index type set to number (and not string) - see https://github.com/Microsoft/TypeScript/issues/17827.
You're getting this error because you have --noImplicitAny turned on. A way to get this working (a hacky way) would be to cast the indexer to a string:
for (x in browserPrefix) {
if (el.style[x as any] !== undefined) {
return browserPrefix[x];
}
}
The other way would be to modify the typings (try bumping the issue on github).
while we're here, you should mark x with const and if you're going to use for-in on an object you should make sure that the property belongs to the object to avoid pulling in anything that is inherited in the prototype chain:
for (const x in browserPrefix) {
if (browserPrefix.hasOwnProperty(x) && el.style[x as any] !== undefined) {
return browserPrefix[x];
}
}
Alternatively, use for-of with Object.keys instead of for-in.
There's no need to define x ahead of time here.
There are several problems in the code, the first one is that IBrowserPrefix is defined as having a string index and thus keyof IBrowserPrefix; will actually be string. I would remove the interface and just use let x: keyof typeof browserPrefix;
The next problem is the way typescript defined the CSSStyleDeclaration interface. It only include standard properties, not vendor specific ones.
You could a type assertion to tell the compiler you know what you are doing and ignore the error
export function whichAnimationEvent() {
const el = document.createElement('temp');
const browserPrefix = {
animation: 'animationend',
OAnimation: 'oAnimationEnd',
MozAnimation: 'animationend',
WebkitAnimation: 'webkitAnimationEnd',
};
let x: keyof typeof browserPrefix;
for (x in browserPrefix) {
if (el.style[x as keyof CSSStyleDeclaration] !== undefined) {
return browserPrefix[x];
}
}
}
You could also extend with CSSStyleDeclaration with the vendor specific keys you require.
Try for (x of Object.keys(browserPrefix)) instead of for (x in browserPrefix).
It's typically frowned upon to use the in keyword for a loop, because you may get properties that do not belong to the object.