How do I convert an Enum to a Constant Class like this, in Typescript? Currently using React Typescript, with functional components .
This question is slightly different: TypeScript enum to object array
export enum ProductStatus {
Draft = 1,
Review = 2,
Approved = 3,
Rejected = 4,
Billed = 5,
Collected = 6,
Unpayable = 7,
WorkInProgress = 8,
ReadyToReview = 9,
NeedsRevision = 10,
Failed = 11,
}
export const ProductStatus = {
Draft: 1,
Review: 2,
Approved: 3,
Rejected: 4,
Billed: 5,
Collected: 6,
Unpayable: 7,
WorkInProgress: 8,
ReadyToReview: 9,
NeedsRevision: 10,
Failed: 11,
};
loop throught the keys and use ethe reduce function
const newConst = React.useMemo(() => {
return Object.keys(ProductStatus).reduce((acc, key) => {
if (isNaN(Number(key))) {
acc[key] = ProductStatus[key];
}
return acc;
}, {});
}, []);
console.log(newConst );
https://stackblitz.com/edit/react-ts-ivfxqw?file=App.tsx
We can use Object.values to get the values from the enum and convert them to an object using reduce.
const ProductStatusObject = Object.values(ProductStatus)
.filter((v): v is keyof typeof ProductStatus => typeof v !== "number")
.reduce((p, c) => {
p[c] = ProductStatus[c]
return p
}, {} as Record<keyof typeof ProductStatus, number>)
ProductStatusObject
// ^? Record<"Draft" | "Review" | "Approved" | ..., number>
As you may know, enums contain a mapping in both directions. We need to filter out the values which are not of type string to only get the keys of the enum. The filter callback also needs a type predicate to indicate to TypeScript that the resulting array only contains values of type keyof typeof ProductStatus.
The reduce method takes the elements of the array and assigns them as keys to an object using the corresponding enum value. I typed the object as Record<keyof typeof ProductStatus, number> to get a useful return type.
console.log(ProductStatusObject)
/*
{
"Draft": 1,
"Review": 2,
"Approved": 3,
"Rejected": 4,
"Billed": 5,
"Collected": 6,
"Unpayable": 7,
"WorkInProgress": 8,
"ReadyToReview": 9,
"NeedsRevision": 10,
"Failed": 11
}
*/
Playground
Related
I am trying to use a Proxy to add logic whenever a value in my array gets set to a new value. However, TypeScript is complaining because it doesn't like that the array gets indexed with something that isn't a number.
const nums = [1, 2, 3, 4, 5];
const handler: ProxyHandler<number[]> = {
set(target: number[], property: string | symbol, newValue: any) {
target[property] = newValue;
return true;
},
};
const proxy = new Proxy(nums, handler);
On the line with target[property] = newValue; I get the error Element implicitly has an 'any' type because index expression is not of type 'number'.ts(7015).
Is there a good way to make TypeScript happy?
Do you index arrays with string? No, you generally index them with number, and that's what TS tells you.
const nums = [1, 2, 3, 4, 5];
const handler: ProxyHandler<number[]> = {
set(target: number[], property: string | symbol | number, newValue: any) {
if (typeof property !== 'number') throw new Error('You can only index array with `number`!')
target[property] = newValue;
return true;
},
};
const proxy = new Proxy(nums, handler);
Playground Link
This question already has answers here:
Dealing with Array shift return type in Typescript 2.0 with strict null checks
(2 answers)
Closed 11 months ago.
I was using Array.prototype.shift() and started wondering if you could use it in an inline-if as it returns an object or undefined.
For example to consume an array element by element:
const array = [1, 2, 3, 4, 5, 6, 7, 8];
const handleData = (n: number) => {
console.log(n);
}
while (true) {
const n = array.shift();
if (n) {
handleData(n);
} else {
break;
}
}
But would it also be possible to use it in an inline-if?
while (true) {
handleData(array.shift() ? /** use object **/ : /** do nothing **/);
}
Just check the length of the array.
const array = [1, 2, 3, 4, 5, 6, 7, 8];
while (array.length) console.log(array.shift());
I'm trying to force an argument of type number[] to contain at least one element of value 9.
So far I've got:
type MyType<Required> = { 0: Required } | { 1: Required } | { 2: Required };
declare function forceInArray<
Required extends number,
Type extends number[] & MyType<Required>
>(
required: Required,
input: Type
): void;
// should fail type-checking
forceInArray(9, []);
forceInArray(9, [1, 2]);
forceInArray(9, { 0: 9 });
// should type-check correctly
forceInArray(9, [9]);
forceInArray(9, [9, 9]);
forceInArray(9, [9, 2, 3, 4]);
forceInArray(9, [1, 9, 3, 4]);
forceInArray(9, [1, 2, 9, 4]);
forceInArray(9, [1, 2, 3, 9]);
Link to TS playground
But ofc the type MyType won't include all possible indexes, so I'm trying to write that in some other way. { [index: number]: 9} is not the good way to do that, since it requires all values to be set to 9. I've also tried some combination of mapped types, with no success
How can I write MyType so that it solves this problem?
You can indeed use mapped types. Here's how I'd type forceInArray():
declare function forceInArray<
R extends number,
T extends number[],
>(required: R, input: [...T] extends { [K in keyof T]: { [P in K]: R } }[number] ?
readonly [...T] : never): void;
Some of the complexity here has to do with convincing the compiler to infer array literal values as tuple types and number literal values as numeric literal types (having [...T] in there deals with both). There's some black magic involved. Also I'd expect some interesting edge cases to crop up around widened types like number, 0-element tuples, etc. Finally, I used readonly arrays so people can use const assertions if they want (as in forceInArray(9, [1,2,9] as const)).
Okay, the heart of the matter: { [ K in keyof T]: { [P in K]: R } }[number] type is very much like your MyType type alias. If T is [4, 5, 6, 7, 8] and R is 9, then that type becomes [{0: 9}, {1: 9}, {2: 9}, {3: 9}, {4: 9}][number], or {0: 9} | {1: 9} | {2: 9} | {3: 9} | {4: 9}. Notice how it expands to have as many terms as the length of T.
Let's see if it works:
forceInArray(9, []); // error
forceInArray(9, [1, 2]); // error
forceInArray(9, { 0: 9 }); // error
forceInArray(9, [9]); // okay
forceInArray(9, [9, 9]); // okay
forceInArray(9, [9, 2, 3, 4]); // okay
forceInArray(9, [1, 9, 3, 4]); // okay
forceInArray(9, [1, 2, 9, 4]); // okay
forceInArray(9, [1, 2, 3, 9]); // okay
forceInArray(9, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // okay
Looks good. Hope that helps; good luck!
Link to code
I have a scan operator with the following arguments:
.scan(function (acc:Message[], x: (msg: Message[]) => Message[]){ return x(acc); }, initialMessages)
my question is what return x(acc) returns actually? how does the accumulator function work in this particular case?
I am quite new to TypeScript and lambda expressions, so any help is much appreciated.
In this case the type of x(acc) would be Message[]. You can tell this from the signature of the 2nd argument x, which returns Message[].
Usually scan() accepts a function (acc, val) => ... with accumulator and value. and emits the returned value. In this case the emitted value val entering the scan operator is a closure. it's a bit unusual, but perfectly fine. An example which outputs
[ 1, 2, 3 ]
[ 3, 4, 5 ]
[ 4, 5, 6 ]
...would be:
import { Observable } from 'rxjs/Rx';
type Message = number;
function scanFn(acc: Message[], x: (msg: Message[]) => Message[]): Message[] {
return x(acc);
}
function mapFn(val: Message): (msg: Message[]) => Message[] {
return x => x.map((y, i) => val + i);
}
Observable.from([1, 3, 4])
.map(mapFn)
.scan(scanFn, [1, 2, 3])
.subscribe(console.log);
I am able to create observable from an array but its type is Observable<number> not Observable<number[]>
getUsers(ids: string[]): Observable<number[]> {
const arraySource = Observable.from([1, 2, 3, 4, 5]);
//output: 1,2,3,4,5
const subscribe = arraySource.subscribe(val => console.log(val));
let returnObserable = Observable.from([1, 2, 3, 4, 5]);
return returnObserable; //error at this line, because of the return type
}
Is there any other way to create observable other than from ?
If you want the entire array to be emitted as a single emissions use Observable.of instead:
const arraySource = Observable.of([1, 2, 3, 4, 5]);
Observable.from iterates the array and emits each item separately while Observable.of takes it as without any further logic.
Alternatively, you could also nest two arrays but that's probably too confusing:
const arraySource = Observable.from([[1, 2, 3, 4, 5]]);