'..' could be instantiated with an arbitrary type which could be unrelated to '...' - typescript-typings

I just trying to implement a hooks for fetching data from window by useState, I'm tripping up over generics and code as below
interface ApplyData {
//...
}
interface ApplyRouterData {
//...
}
export const useApplyRouterData = () => useWindowData<ApplyRouterData>();
export const useApplyData = () => useWindowData<ApplyData>();
export const useWindowData = <T extends ApplyData | ApplyRouterData>() => {
return useState<T>(() => {
return window.data;
});
};
I also declare type for window.data like this
declare global {
interface Window {
data: ApplyRouterData | ApplyData;
}
}
but I got complier error om my hook
'T' could be instantiated with an arbitrary type which could be unrelated to 'IApplyResp | IApplyRouterResp'.
the type of window.data and generics are same as IApplyResp | IApplyRouterResp in my view, why? Thanks for you answer.

Related

Accessing inline type in the constructor

I have this:
export class QueueEntity<T> implements HasInternalQueue<T> {
opts: { // <--- inline type here
foo: boolean
}
constructor(v: typeof this.opts) { // this doesn't quite work
this.opts = v
}
}
is there a way to reference the inline type or is this not possible?
Since there is no way to define a type inside the class directly (An open issue in the TS repo). I think you can use the class name to reference this:
export class QueueEntity<T> {
opts: { // <--- inline type here
foo: boolean
}
constructor(v: QueueEntity<T>['opts']) { // <-- should work now
this.opts = v
}
}
const obj = new QueueEntity({ foo : false });
const obj2 = new QueueEntity({ foo2 : false });
const obj3 = new QueueEntity();
Playground

Typescript: Generic type of method params to match type of callback function params

I'm trying to make a class that accepts a function in the constructor. The function can have arguments of any type. Then I want to put a method on the class that accepts that same arguments as function parameter, as it will be a wrapper around this callback. Here's a simplified example to show what I'm trying to do
interface Options<T> {
callbackFn(...x: any[]) => Promise<T>
}
class ExampleClass<T> {
private options: Options<T>;
result: T;
constructor(options: Options<T>) {
this.options = options;
}
async wrapperFn(...x: any[]) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(x)
// Do some stuff after
}
}
const example = new ExampleClass<string>({
callbackFn: (a: string, b:string) => new Promise((res) => {
res(a + b);
})
});
example.wrapperFn("foo", "bar")
This is basically the way I have it now, and it works but it obviously doesn't enforce the types of the params of wrapperFn which isn't ideal. Is there any way to do something like this?
If you want the compiler to keep track of both the callback return type and the callback argument list type, then you'll want Options to be generic in both the return type (you called it T but I'll call it R for "return") and the argument list type (I'll call it A for "arguments"):
interface Options<A extends any[], R> {
callbackFn(...x: A): Promise<R>
}
Now you can just use A anywhere you were using any[] before, and you'll get stronger typing. This also implies that ExampleClass needs to be generic in A and R too:
class ExampleClass<A extends any[], R> {
private options: Options<A, R>;
result?: R;
constructor(options: Options<A, R>) {
this.options = options;
}
async wrapperFn(...x: A) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(...x)
// Do some stuff after
}
}
Let's test it out:
const example = new ExampleClass({
callbackFn: (a: string, b: string) => new Promise<string>((res) => {
res(a + b);
})
});
// const example: ExampleClass<[a: string, b: string], string>
example.wrapperFn("foo", "bar") // okay
example.wrapperFn("foo", 123); // error!
// --------------------> ~~~
// Argument of type 'number' is not assignable to parameter of type 'string'.
Looks good.
Playground link to code

It's not possible to mock classes with static methods using jest and ts-jest

I have two classes that simulate a simple sum operation.
import SumProcessor from "./SumProcessor";
class Calculator {
constructor(private _processor: SumProcessor) { }
sum(a: number, b: number): number {
return this._processor.sum(a, b)
}
}
export default Calculator
And the operation processor.
class SumProcessor {
sum(a: number, b: number): number {
return a + b
}
static log() {
console.log('houston...')
}
}
export default SumProcessor
I'm tryng to mock the class SumProcessor to write the following unit test using jest+ts-jest.
import Calculator from "./Calculator"
import SumProcessor from "./SumProcessor"
import { mocked } from "ts-jest/utils"
jest.mock('./SumProcessor')
describe('Calculator', () => {
it('test sum', () => {
const SomadorMock = <jest.Mock>(SumProcessor)
SomadorMock.mockImplementation(() => {
return {
sum: () => 2
}
})
const somador = new SomadorMock()
const calc = new Calculator(somador)
expect(calc.sum(1, 1)).toBe(2)
})
})
When the static method is present in class SumProcessor, the mock code const SomadorMock = (SumProcessor) indicates the following compilation error:
TS2345: Argument of type '() => jest.Mock<any, any>' is not assignable to parameter of type '(values?: object, option
s?: BuildOptions) => SumOperator'.
Type 'Mock<any, any>' is missing the following properties from type 'SumOperator...
If the static method is removed from SumProcessor class, everything work's fine.
Can anybody help?
since you have already mocked the SumProcessor class with jest.mock('./SumProcessor'); you can just add a spy to the method you would like to mock, for an example:
jest.spyOn(SumProcessor.prototype, 'sum').mockImplementation(() => 2);
this way your test class would look something like this:
import Calculator from "./Calculator"
import SumProcessor from "./SumProcessor"
jest.mock('./SumProcessor')
describe('Calculator', () => {
it('test sum', () => {
jest.spyOn(SumProcessor.prototype, 'sum').mockImplementation(() => 2);
const somador = new SumProcessor();
const calc = new Calculator(somador)
expect(calc.sum(1, 1)).toBe(2)
})
})
much simpler, right?

Unexpected "Spread types may only be created from object types" error when using generics

I've got this typescript class that requires a generic type to be provided on construction:
type Partial<T> = {
[P in keyof T]?: T[P];
};
class Foo<Bar> {
bis: Partial<Bar> = {}; // (1)
constructor() {
console.log(typeof this.bis); // object
this.bis = {...this.bis}; // (2) Spread types may only be created from object types
}
}
How ever, as you can see above, i don't get an error at (1), but i do at (2).
Why is this? And how do i fix it?
Edit1:
I've opened an issue over at the Typescript github.
A workaround for this is typecasting the object explicitely with <object>,<any> or <Bar> in your case.
I don't know if your requirements allow this or not but have a look -
type Partial<T> = {
[P in keyof T]?: T[P];
};
class Foo<Bar> {
bis: Partial<Bar> = {}; // (1)
constructor() {
console.log(typeof this.bis); // object
this.bis = {...<Bar>this.bis};
}
}

How to override a property to be non-nullable in Typescript

The DefinitelyTyped definition of the Node built-in IncomingMessage (the type of req in the (req, res, next) arguments) has defined url to be nullable. Here's the snipped parts of the definition file:
// #types/node/index.d.ts
declare module "http" {
export interface IncomingMessage {
/**
* Only valid for request obtained from http.Server.
*/
url?: string;
}
}
As the comment says, this is because this property is only valid when you're getting an instance of this IncomingMessage from the http.Server. In other uses it won't exist, hence, it's nullable.
However, in my case, I know that I'm only getting these instances from http.Server, and so it's kinda annoying that I can't just access the property without extra guards.
import { IncomingMessage, ServerResponse } from 'http';
function someMiddleware(req: IncomingMessage, res: ServerResponse, next: Function) {
const myStr: string = req.url; // bzzzt.
// Argument of type 'string | undefined' is not
// assignable to parameter of type 'string'.
}
It's probably good to mention that I'm using TS 2.0.3 with strictNullChecks, which is not enabled on the Typescript Playground.
Here's the question. Is it possible to override that definition across my application so that url is not nullable?
Here's what I've already tried... adding this to one of my files:
declare module 'http' {
interface IncomingMessage {
url: string;
}
}
...however that is disallowed: "Subsequent variable declarations must have the same type". This is explained in the documentation.
The only thing I can think of thus far is to create my own module which imports, extends and then exports the interfaces:
// /src/http.ts
import { IncomingMessage as OriginalIM } from 'http';
export interface IncomingMessage extends OriginalIM {
url: string;
}
// src/myapp.ts
import { IncomingMessage } from './http'; // <-- local def
function someMiddleware(req: IncomingMessage) {
const str: string = req.url; // all good
}
So, this works, but it seems so wrong.
As of TypeScript 2.1, you can use a lookup type to access an interface property.
IncomingMessage['url'] // string | undefined
You can combine that with NonNullable to fit your use case.
NonNullable<IncomingMessage['url']> // string
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html
So I found a solution which is slightly less hacky.
TypeScript 2.0 also has added a non-null assertion operator: !
function someMiddleware(req: IncomingMessage) {
const str1: string = req.url; // error, can't assign string | undefined to string
const str2: string = req.url!; // works
}
In my case, it's still a bit annoying, since there are many different files which need to access this property and so this non-null assertion is used in many places.
In your sample case, it's easy because you want to get rid of ALL undefined, therefore use the Required utility type.
interface IncomingMessage { url?: string; }
type ValidMessage = Required<IncomingMessage>;
ValidMessage will have all properties required.
But for those coming here to find out how to get rid of ALL null, you can use this custom utility type.
export type NonNullableFields<T> = {
[P in keyof T]: NonNullable<T[P]>;
};
interface IncomingMessage { url: string | null; }
type ValidMessage = NonNullableFields<IncomingMessage>;
ValidMessage will have all properties not null.
And for those coming here to find out how to get rid of null only for specific fields, you can use these custom utility types.
export type NonNullableFields<T> = {
[P in keyof T]: NonNullable<T[P]>;
};
export type NonNullableField<T, K extends keyof T> = T &
NonNullableFields<Pick<T, K>>;
interface IncomingMessage { url: string | null; }
type ValidMessage = NonNullableField<IncomingMessage, 'url'>;
ValidMessage will have the property url not null.
Here's a solution defining a utility type RequiredProperties:
type RequiredProperties<T, P extends keyof T> = Omit<T, P> & Required<Pick<T, P>>;
Example usage:
type Foo = {
a?: any;
b?: any;
c?: any;
};
type Bar = RequiredProperties<Foo, 'a' | 'b'>;
const bar1: Bar = { a: 1, b: 2, c: 3 };
const bar2: Bar = { b: 2, c: 3 }; // fails because `a` is now required
const bar3: Bar = { c: 3 }; // fails because both `a` and `b` are missing

Resources