I am trying to define a helper function that will parse an axios error and store the resultant error message into the specified field of the specified object.
I want the calling site to be:
axios.get(...).then(...).catch(ParseIntoErrorString(myClass, 'loadError');
where myClass is a TypeScript class that contains a method that is calling this code (probably from a 'loadData' method). myClass will likely be replaced with 'this' in most cases.
loadError is the name of a string property on myClass. I am modeling this after jest.spyOn, where you specify the object that you want to spy on and then the string argument is the name of the function you want to replace with a spy function. jest.spyOn has a nice property that it will give you an error if you provide a string that isn't a function of the spied on object.
I have tried something like this, but it isn't working at all:
type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K }[keyof T] &
string;
export function ParseErrorIntoString<T extends {}, P extends NonFunctionPropertyNames<Required<T>>>(obj: T, member: P): (reason: AxiosError) => void {
return (reason: AxiosError): void => {
// eslint-disable-next-line no-param-reassign
obj[member] = AjaxParseError(reason);
};
}
The NonFunctionPropertyNames was lifted right from the jest typings file.
And besides not working at the call site, this code itself throws up two errors:
- it complains that I can't use extends {} and suggests that I replace it with
Record<string, unknown>, which is fine, but when I do that, I can't passthisto it at the call site because it says thatthisdoesn't conform toRecord<string, unknown>. It doesn't tell me why. - the
obj[member]says that I can't store a string into it. I suspect this is because there is no restriction of theNonFunctionPropertyNamesthat limits it to only allow string properties.
I have also tried this:
export function ParseErrorIntoString<T extends object>(obj: T, member: Extract<keyof T, string | null>): (reason: AxiosError) => void {
return (reason: AxiosError): void => {
// eslint-disable-next-line no-param-reassign
obj[member] = AjaxParseError(reason);
};
}
My expectation is that Extract<keyof T, string | null> will select all of the keys of T that are of type "string | null".
But it does not. It does give me an error at the calling site if I pass in a non-member in the string, but it is too accepting. It will also allow me to provide the name of a boolean member without throwing an error.
Which is probably why it still throws an error at the obj[member] = ... line, saying that I can't assign a string to obj[member].
Also, I have to explicitly state T at the calling site for it to accept a member of T. I was assuming that like spyOn in jest, it would infer the type of T from the first argument.
Here is a more stand alone full example. This is the third attempt that is getting very close:
class cls {
public str: string | null;
public bln: boolean;
constructor() {
this.str = "";
this.bln = false;
}
}
const instance = new cls();
type StringPropertyNames<T> = { [K in keyof T]: T[K] extends string | null ? K : never }[keyof T] & string;
function f<T extends cls>(obj: T, p: StringPropertyNames<T>): void {
obj[p] = "done";
}
f(instance, 'str'); // I want to be able to call f, with the instance and the property like this
console.log(instance.str) // should print out 'done'
in this complete example, f is the original ParseErrorIntoString function.
In this third example, the calling site of f(instance, 'str') works as expected. I get an error when I try to call f(instance, 'bln') because bln is type boolean, not type string.
But I am still getting "Type 'string' is not assignable to type T[StringPropertyNames<T>]" on the obj[p] = 'done' line.
I think the StringPropertyNames<T> needs to be given some sort of return type to say that it is only returning keys of type string | null.
Any suggestions on the proper typing of the ParseErrorIntoString function?