TypeScript error with Symbol.species example from MDN - javascript

With this code (which is equivalent to a sample at MDN):
class MyArray<T> extends Array<T> {
static get [Symbol.species]() { return MyArray }
}
I get this error (with TypeScript 3.8.3):
Class static side 'typeof MyArray' incorrectly extends base class static side '{ isArray(arg: any): arg is any[]; readonly prototype: any[]; from<T>(arrayLike: ArrayLike<T>): T[]; from<T, U>(arrayLike: ArrayLike<T>, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; from<T>(iterable: Iterable<...> | ArrayLike<...>): T[]; from<T, U>(iterable: Iterable<...> | ArrayLike<...>, mapfn: (v: T, k: nu...'.
Types of property '[Symbol.species]' are incompatible.
Type 'typeof MyArray' is not assignable to type 'ArrayConstructor'.
Type 'typeof MyArray' provides no match for the signature '(arrayLength?: number | undefined): any[]'.
What could be wrong?

Related

Define a type with only one property and the name of that property must be in a predefined set of names

Let's say I want to create an error structure that indicates if the error is on the server side or the client side. Example:
const serverErr = {
server: "Error!"
};
const clientErr = {
client: "Error!"
};
The error object must have only one property, and the name of that property must be server or client.
I tried the answer to this question question but it's not working.
According to this answer, here is the definition of IsSingleKey:
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
? I
: never;
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
type ISingleKey<K extends string, T> = IsUnion<K> extends true ? "Can only contain a single key" : Record<K, T>;
One approach is to make an error type AllowedErrors which is a union of all the allowable types, each member of which has exactly one of the acceptable keys as required, and prohibits defined values for the other keys. It would look like this:
type AllowedErrors =
{ server: string; client?: never; x?: never; y?: never; z?: never; } |
{ client: string; server?: never; x?: never; y?: never; z?: never; } |
{ x: string; server?: never; client?: never; y?: never; z?: never; } |
{ y: string; server?: never; client?: never; x?: never; z?: never; } |
{ z: string; server?: never; client?: never; x?: never; y?: never; };
Note that TypeScript doesn't explicitly support prohibiting a property key, but you can make it an optional property whose value type is the impossible never type, then the only value you might find at that key would be undefined.
This should behave how you want:
const serverErr: AllowedErrors = {
server: "Error!"
};
const clientErr: AllowedErrors = {
client: "Error!"
};
const bothErr: AllowedErrors = {
server: "Error",
client: "Error"
} // error! Type '{ server: string; client: string; }'
// is not assignable to type 'AllowedErrors'
const neitherErr: AllowedErrors = {
oops: "Error"
} // Type '{ oops: string; }' is not assignable
// to type 'AllowedErrors'.
const okayErr = Math.random() < 0.5 ? { x: "" } : { y: "" };
So, that's great.
But obviously you don't want to have to write or modify your AllowedErrors type manually. You want to generate it from a union of the keys like
type AllowedErrorKeys = "server" | "client" | "x" | "y" | "z";
so that you just need to add/remove things there. Well, here's one approach:
type SingleKeyValue<K extends PropertyKey, V, KK extends PropertyKey = K> =
K extends PropertyKey ?
Record<K, V> & { [P in Exclude<KK, K>]?: never } extends infer O ?
{ [P in keyof O]: O[P] } : never : never;
The type SingleKeyValue<K, V> produces the relevant union where each member has only one allowed key from K with value type V and the remainder of the keys from K are prohibited.
First, the type is wrapped with the seemingly no-op K extends PropertyKey ? ... : never, which actually a distributive conditional type that splits K up into its individual union members, operates on each member, and then unites the results back into another union.
The inner type is essentially Record<K, V> & { [P in Exclude<KK, K>]?: never }; the first piece using the Record<K, V> utility type to represent an object with the one key from K and the value type V, which is intersected with a type prohibiting all keys from Exclude<KK, K> using the Exclude<T, U> utility type. Wait, what's KK? That's a "dummy" type parameter I declared in the outer scope which defaults to K. This is really just a trick so that K is broken into its union pieces while KK is a copy of the original union K, so that we can express "all the members of the original union except htis one".
Anyway that's the type we need except it's pretty ugly (an intersection of utility types), so I use a technique described at How can I see the full expanded contract of a Typescript type?, ... extends infer O ? { [P in keyof O]: O[P] } : never, to copy the type to another type parameter and iterate its properties with a mapped type to get a single object type.
And now we can just write
type AllowedErrors = SingleKeyValue<AllowedErrorKeys, string>;
and use IntelliSense to verify that it evaluates to
/* type AllowedErrors =
{ server: string; client?: never; x?: never; y?: never; z?: never; } |
{ client: string; server?: never; x?: never; y?: never; z?: never; } |
{ x: string; server?: never; client?: never; y?: never; z?: never; } |
{ y: string; server?: never; client?: never; x?: never; z?: never; } |
{ z: string; server?: never; client?: never; x?: never; y?: never; }*/
as desired.
Playground link to code

Dynamically import one Icon using icon-name from react-icons in nextjs/typescript

I'm trying to dynamically import an icon from react-icons following the this Nextjs instructions but I get an error.
This is my code:
import React, { Suspense } from 'react';
import dynamic from 'next/dynamic';
interface IconProps {
name: string;
}
function Icon({ name }: IconProps) {
const MyIcon = dynamic(() => import('react-icons/fi/index.js').then((mod) => mod[name]), {suspense: true});
return (
<Suspense fallback={`Loading...`}>
<MyIcon />
</Suspense>
);
}
export default Icon
This is the error:
Argument of type '() => Promise<IconType | React.ComponentClass<never, any> | React.FunctionComponent<never> | { default: React.ComponentType<never>; }>' is not assignable to parameter of type 'DynamicOptions<{}> | Loader<{}>'.
Type '() => Promise<IconType | React.ComponentClass<never, any> | React.FunctionComponent<never> | { default: React.ComponentType<never>; }>' is not assignable to type '() => LoaderComponent<{}>'.
Type 'Promise<IconType | ComponentClass<never, any> | FunctionComponent<never> | { default: ComponentType<never>; }>' is not assignable to type 'LoaderComponent<{}>'.
Type 'IconType | ComponentClass<never, any> | FunctionComponent<never> | { default: ComponentType<never>; }' is not assignable to type 'ComponentType<{}> | { default: ComponentType<{}>; }'.
Type 'ComponentClass<never, any>' is not assignable to type 'ComponentType<{}> | { default: ComponentType<{}>; }'.
Type 'ComponentClass<never, any>' is not assignable to type 'ComponentClass<{}, any>'.
Types of property 'getDerivedStateFromProps' are incompatible.
Type 'GetDerivedStateFromProps<never, any> | undefined' is not assignable to type 'GetDerivedStateFromProps<{}, any> | undefined'.
Type 'GetDerivedStateFromProps<never, any>' is not assignable to type 'GetDerivedStateFromProps<{}, any>'.
Types of parameters 'nextProps' and 'nextProps' are incompatible.
Type 'Readonly<{}>' is not assignable to type 'never'.ts(2345)
What am I doing wrong?

MatIconModule no longer compiles in libraries

I'm trying to use the MatIconModule in a Angular Package Format library and when I introduce the MatIconModule it fails to compile. To see for yourself clone the MVCE repository:
git clone git#github.com:fireflysemantics/maticonmoduleerror.git
cd maticonmoduleerror
npm i
ng build --prod fs-assets
This is what the module looks like:
import { NgModule } from '#angular/core';
import { MatIconModule } from '#angular/material/icon';
#NgModule({
declarations: [
],
imports: [MatIconModule
],
exports: [
]
})
export class FsAssetsModule { }
If MatIconModule is removed, it compiles. Angular Material was added to the root project the standard way with ng add #angular/material. These are the compiler errors:
> ole#mkt:~/Temp/clone/maticonmoduleerror$ ng build --prod fs-assets
Building Angular Package
------------------------------------------------------------------------------
Building entry point '#fireflysemantics/assets'
------------------------------------------------------------------------------
✖ Compiling TypeScript sources through NGC
ERROR: projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:64 - error TS1005: ';' expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:69 - error TS1109: Expression expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:82 - error TS1011: An element access expression should take an argument.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:83 - error TS1005: ';' expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:85 - error TS1128: Declaration or statement expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:55 - error TS2304: Cannot find name 'abstract'.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~~~~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:78 - error TS2693: 'any' only refers to a type, but is being used as a value here.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:88 - error TS2304: Cannot find name 'T'.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~
projects/fs-assets/node_modules/#angular/material/core/option/optgroup.d.ts:16:22 - error TS2420: Class '_MatOptgroupBase' incorrectly implements interface 'CanDisable'.
Property 'disabled' is missing in type '_MatOptgroupBase' but required in type 'CanDisable'.
16 export declare class _MatOptgroupBase extends _MatOptgroupMixinBase implements CanDisable {
~~~~~~~~~~~~~~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/disabled.d.ts:12:5
12 disabled: boolean;
~~~~~~~~
'disabled' is declared here.
projects/fs-assets/node_modules/#angular/material/icon/icon.d.ts:60:22 - error TS2420: Class 'MatIcon' incorrectly implements interface 'CanColor'.
Type 'MatIcon' is missing the following properties from type 'CanColor': color, defaultColor
60 export declare class MatIcon extends _MatIconMixinBase implements OnInit, AfterViewChecked, CanColor, OnDestroy {
~~~~~~~
An unhandled exception occurred: projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:64 - error TS1005: ';' expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:69 - error TS1109: Expression expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:82 - error TS1011: An element access expression should take an argument.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:83 - error TS1005: ';' expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:85 - error TS1128: Declaration or statement expected.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:55 - error TS2304: Cannot find name 'abstract'.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~~~~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:78 - error TS2693: 'any' only refers to a type, but is being used as a value here.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/constructor.d.ts:14:88 - error TS2304: Cannot find name 'T'.
14 export declare type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
~
projects/fs-assets/node_modules/#angular/material/core/option/optgroup.d.ts:16:22 - error TS2420: Class '_MatOptgroupBase' incorrectly implements interface 'CanDisable'.
Property 'disabled' is missing in type '_MatOptgroupBase' but required in type 'CanDisable'.
16 export declare class _MatOptgroupBase extends _MatOptgroupMixinBase implements CanDisable {
~~~~~~~~~~~~~~~~
projects/fs-assets/node_modules/#angular/material/core/common-behaviors/disabled.d.ts:12:5
12 disabled: boolean;
~~~~~~~~
'disabled' is declared here.
projects/fs-assets/node_modules/#angular/material/icon/icon.d.ts:60:22 - error TS2420: Class 'MatIcon' incorrectly implements interface 'CanColor'.
Type 'MatIcon' is missing the following properties from type 'CanColor': color, defaultColor
60 export declare class MatIcon extends _MatIconMixinBase implements OnInit, AfterViewChecked, CanColor, OnDestroy {
~~~~~~~
See "/tmp/ng-14VpXS/angular-errors.log" for further details.
This used to work fine. Any ideas?
Your project is still using Angular 11, see the package.json in the linked repository. Updating to Angular 12 using ng update #angular/core #angular/cli #angular/material should fix the issue.

TSX: Props type definition for a number input

In JSX/TSX syntax from react, all inputs seems to be define with the same props declaration which is InputHTMLAttributes:
interface InputHTMLAttributes<T> extends HTMLAttributes<T> {
accept?: string;
alt?: string;
autoComplete?: string;
autoFocus?: boolean;
capture?: boolean | string; // https://www.w3.org/TR/html-media-capture/#the-capture-attribute
checked?: boolean;
crossOrigin?: string;
disabled?: boolean;
form?: string;
formAction?: string;
formEncType?: string;
formMethod?: string;
formNoValidate?: boolean;
formTarget?: string;
height?: number | string;
list?: string;
max?: number | string;
maxLength?: number;
min?: number | string;
minLength?: number;
multiple?: boolean;
name?: string;
pattern?: string;
placeholder?: string;
readOnly?: boolean;
required?: boolean;
size?: number;
src?: string;
step?: number | string;
type?: string;
value?: string | ReadonlyArray<string> | number;
width?: number | string;
onChange?: ChangeEventHandler<T>;
}
I want to define a specific type for only an input of type number so that TypeScript complains if I use a checked or disabled prop on it:
<!-- invalid `checked` prop -->
<input type="number" step="any" min="0" max="100" value="22.33" checked={true} />
So is there already a type definition for that coming from React?
I know that I can just define it like the following but I would prefer to use an official source:
interface InputNumberProps {
type: 'number';
max?: number | string;
min?: number | string;
...
}
I think the simplest solution would be to wrap input into your own component and provide custom type definitions. As far as I know, there is nothing like that coming from official React types. This is done because HTML itself is not strongly typed and it is valid to do something like <input type="number" checked />
As for type definition something like that would suffice:
interface NumberInputProps {
type: "number"
value: number
min?: number
max?: number
}
interface TextInputProps {
type?: "text"
value: string
}
interface CheckboxInputProps {
type: "checkbox"
checked?: boolean
}
type InputProps = NumberInputProps | TextInputProps | CheckboxInputProps
const Input: React.FC<InputProps> = (props) => <input {...props} />
That is a lot of typings for sure (and this does not event include things like events, class names etc). Also, this requires even more JS code if you want to provide stronger types for something like number fields (converting strings into numbers etc.)
If you want to modify the original InputHTMLAttributes (which is a better solution than writing your own types from scratch) interface, overriding and/or omitting some of the fields there are helper types like Omit that would allow you to do so.
type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "checked" | "min" | "max"> & (NumberInputProps | TextInputProps | CheckboxInputProps)
You can also split different input types into different components if you find writing too much logic in one component. Do whatever suits you
P.S. on the second note. This sounds like type over-engeneering
You can use and extends the official types:
export type InputProps = React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>;
interface NumberInputProps extends Omit<InputProps, 'checked' | 'disabled'> {
value: number
}
Using this code you exclude checked and disabled properties and overrides value to allow only numbers instead string | ReadonlyArray | number

Subclass method signature inference

I have the following abstract class
// AbstractFiller.ts
export abstract class AbstractFiller {
public abstract fill(data: number | string | boolean | Date, numberOfFillers: number): string;
}
and several filler subclasses
export class WhiteSpacesFiller extends AbstractFiller {
public fill(data: number | string | boolean | Date, numberOfFillers: number): string {
// logic
}
}
export class ZerosFiller extends AbstractFiller {
public fill(data: number | string | boolean | Date, numberOfFillers: number): string {
// logic
}
}
// ...etc
Is there a way that TS would infer the method signature from the abstract class so that I have:
No type duplication in every subclass
Strict signature enforcing e.g. removing number from the type of data in a subclass will not throw an error.
Typescript will not infer method parameters from base class. The way it works is that after the class is typed, the class is checked for compatibility with the base class. This mean that a parameter in a derived class can be of a derived type (this is not sound but class method parameters relate bivariantly even under strict null checks).
One thing that can be done to reduce the amount of type duplication is to use Parameters with rest parameter destructuring.
export abstract class AbstractFiller {
public abstract fill(data: number | string | boolean | Date, numberOfFillers: number): string;
}
export class WhiteSpacesFiller extends AbstractFiller {
public fill(...[data, numberOfFillers]: Parameters<AbstractFiller['fill']>): string {
return ""
}
}
Excuse me if I misunderstand the question, but I think you can solve your problem with generics i.e. have your AbstractFiller class be generic.
That would look something like this:
export abstract class AbstractFiller<T> {
public abstract fill(data: T, numberOfFillers: number): string;
}
export class WhiteSpacesFiller extends AbstractFiller<string> {
public fill(data: string, numberOfFillers: number) {
//logic
return data;
}
}
export class ZerosFiller extends AbstractFiller<number> {
public fill(data: number, numberOfFillers: number) {
return data.toString();
}
}

Categories

Resources