React useCallback in typescript - reactjs

Type error: Argument of type 'Function' is not assignable to parameter of type '(...args: any[]) any
Type 'Function' provides no match for the signature '(...args: any[]): any'.
const handleImageSearch = useCallback(
debounce((value) => searchUnsplashImage(value), 400),
^
[],
);
I have tried adding the
interface IFunction {
(...argArray: any[]): any;
}
const handleImageSearch = useCallback<IFunction>(
debounce((value) => searchUnsplashImage(value), 400),
^
[],
);
Befor adding props
But it does not work. Please help me with this.

oneI have been facing this issue on .jsx files, and I assume this happens only for tsc compilations for non tsx? files (not sure, though). Also, the weird point is, this is not getting detected by VSCode as an error.
Anyways, the issue is quite clear, it happens because lodash/debounce returns a DebouncedFunc, as can be seen in the below signature:
debounce<T extends (...args: any) => any>(func: T, wait?: number, options?: DebounceSettings): DebouncedFunc<T>;
To get around this you can try one of the followings:
Disable ts check for that debounce call, i.e. add the following comment above debounce() function call
useCallback(
// #ts-ignore
debounce(),
[]
);
Better way, instead of ignoring ts-checks completely is to, separate the function into a different instance:
const debounceFn = debounce(...);
const callback = useCallback((...args) => debounceFn(...args), []);
Or even more concise:
const callback = useCallback((...args) =>
debounce((param1, paramN) => {
/** Your Function Body */
})(...args)
, []);
Obviously, this is a workaround, and adds more code. For me // #ts-ignore seems like a better option.
A much better solution would be to override React Types itself for useCallback, details: How to overwrite incorrect TypeScript type definition installed via #types/package, but that's all a personal preference.
I hope this fixes your issue.

Related

Using Antd's message with typescript

I am using
"antd": "^4.21.3", "typescript": "^4.7.4" with react 18, and I am facing the problem of error config type! The error message is below..
// current code
import { message } from 'antd'
function AlertModal({ type = 'success', duration = 3, ...props }) {
const config = {
duration: duration,
...props,
}
return message[type as keyof typeof message](config)
}
export default AlertModal
Does anyone have any idea how can I solve this annoying problem??
I'll just rewrite what's in the comments with some extra details, so that this question can be treated as answered.
The message import references the default export of this file which is typed as MessageApi, and the expression message[type as keyof typeof message] is evaluated as follows:
(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose) => MessageType
| (args: ArgsProps) => MessageType
| (options: ConfigOptions) => void
| (messageKey?: React.Key) => void
| () => [MessageInstance, React.ReactElement]
TypeScript in this scenario regards all of these function typings as equally possible so it tries to cast the config object as the first argument as each of those function types.
The problem arises when trying to cast an object to React.Key which is defined as string | number. config (or any object type) is just not a valid argument for that case and TypeScript is doing its job in warning you about that case.
However, in the implementation we can probably assume that certain special message types will not be called, such as the message type destroy. MessageApi['destroy'] is the only MessageApi function property that uses the React.Key argument, so removing (i.e. omitting) that typing would solve the issue. This can be easily done just by changing the keyof message cast to:
return message[type as keyof Omit<typeof message, 'destroy'>](config)

Specifying array of functions with unknown arguments throws a typeerror

I've set-up a rather simple hook called useField.ts, which is defined in the following manner:
type CheckFunction = <T>(input: T, ...args: unknown[]) => unknown
export const useField = <T>(
input: T,
...checkFunctions: CheckFunction[]
) => {
// .... and the code goes here, but irrelevant for the question
}
Afterwards I've begun writing my tests in useField.test.tsx in the following way:
import { renderHook } from '#testing-library/react-hooks'
import { useField } from '../useField'
describe('useField error functions', () => {
const emailRegex = /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/gi
const isValidEmail = (input: string): boolean => !!input.match(emailRegex)
test('Basic checkFunction', () => {
const { result } = renderHook(() => useField('valid#email.com', isValidEmail)
// ^ I get a type-error here
})
})
The line where I try to pass the isValidEmail function has the following typescript error:
Argument of type '(input: string) => boolean' is not assignable to parameter of type 'CheckFunction'.
Types of parameters 'input' and 'value' are incompatible.
Type 'T' is not assignable to type 'string'.
Now this could be technically solved by assigning the definition of checkFunctions to any but it's something I want to be wary of as it kind of defeats the purpose of type-checking.
I also thought about using a generic, but issue is, the arguments can be of various types and that can't be expressed by an array of a single generic
Any idea what I'm doing wrong here? Because from a logical standpoint the usage of unknown seems correct here.
Assuming your check functions only accept one argument, why do you define it as accepting multiple values? E.g. the ...args: unknown[]. You'd also specify that it returns a boolean instead of just unknown.
You can do something like this, which actually uses a generic but seems to work fine for your use case, as far as I can tell:
export const useField = <T>(
input: T,
...checkFunctions: Array<(value: T) => boolean>
) => {
// .... and the code goes here, but irrelevant for the question
}
const result = useField('someString', validator1, validator2);
// ^ It would know T is a string here, since the input is a string
// Therefore it'll check that `validator1` is of the type `(value: string) => boolean`
const result = useField('someString', str => {
// TypeScript knows the type of `str` is string
return str.includes('something');
});
// This would error:
const result = useField<string>(123, validator1);
// As well as this due to the validator accepting the wrong value:
const result = useField('string', (value: number) => true);
This is assuming that useField expects the actual inputted value, and not the id/name of an <input> element. Otherwise there's no reason to have input be generic (after all, the name/id is always a string) and you wouldn't name an <input> valid#email.com but email instead.
For the updated code in the question.
You're defining your "generic" CheckFunction type incorrectly. Use this instead:
type CheckFunction<T> = (input: T, ...args: unknown[]) => unknown
export const useField = <T>(
input: T,
...checkFunctions: CheckFunction<T>[]
) => {
// .... and the code goes here, but irrelevant for the question
}
Your version, where you use type Type = <T>(... basically tells TypeScript "this function has a generic type but it doesn't matter outside Type". While if you use type Type<T>, you tell TypeScript that that type matters, e.g. it should be specified by the user of the type.
In short, by using type Type<T> you allow your useField to specify what T inside Type represents, e.g. the same type as useField's T.

Typescript how to mix dynamic([key: type]: type) and static typing for an interface

Main issue
Not really sure if this is possible, but since i abhor Typescript and it is making my coding hard I figured I'd ask just to make sure.
interface ISomeInterface {
handler: () => {}
[key: string]: string
}
Problem obviously is, the handler is a () => {} and not a string, so typescript throws an error. Is it possible to somehow make this work?
Addendum:
While on the topic, this interface is suppose to be consumed by a React.Context and since a requirement of a default value by React.createContext(/* requires default here */), I need to give it a object that matches that interface. When I attempt to pass an object litteral as such:
React.createContext({handler: () => {throw new Error('using default context')}})
Typescript is making a ruckus by saying {handler: () => never} cannot be assigned to {handler: () => {}} any way to make this interface plausible? I tried to make interface implement () => any, still didn't work, it wants explicit never.
Edit #1:
So i've made some changes, namely:
interface IFormContext {
$handleChange: (key?, val?) => void
$registerField: (key?) => void
[key: string]: { value: string } | ((key?, val?) => void)
}
But now when I am trying to access the field like this: const {[key]: { value }} = props which is basically de-structuring with a dynamic key, i get the following error:
Property 'value' does not exist on type '{ value: string; } | ((key?: any, val?: any) => void)'.ts(2339)
Unfortunately your named properties must match the index signature.
In this case, since at least one property is a function, you can use:
[key: string]: string | Function;
or
[key: string]: any;
Both should work.
For your addendum, () => {} means a function with no arguments that returns a empty object, while the function () => {throw new Error('using default context')} is returning nothing, thus the error.
There is a little bit of confusion with the braces used in types and in the actual function implementation.
A function with the type () => {} is actually something like () => {return {}} or () => ({}).
For a function that returns nothing you can use never or void, but if your handler may return something, you can use something like handler: () => void | boolean, replacing boolean by the type it may return.

is there any way we can define the types for the addEventListener and classList in a react typescript project

In our project(React with typescript) we are using the below code to add and manipulate the underline for the tabs in navigation bar.
public componentDidMount() {
const tabs = document.getElementsByClassName("tab");
Array.prototype.forEach.call(tabs, (tab: EventListener) => {
tab.addEventListener("click", setActiveClass);
});
const setActiveClass = (event: any) => {
Array.prototype.forEach.call(tabs, (tab: any) => {
tab.classList.remove("active");
});
event.currentTarget.classList.add("active");
};
}.
The typescript is throwing following errors :
"[ts] Property 'addEventListener' does not exist on type 'EventListener'" and
"[tslint] Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. (no-any)"
I would like to add appropriate types instead of using "any" in the code. I would also don't want to relax the tslint rule for "any"
Any help would be much appreciated.
Thanks
Using call is inherently not type safe. The definition of call will basically erase any function type information as it uses any:
call(this: Function, thisArg: any, ...argArray: any[]): any;
If you call forEach as a normal instance function the type of the parameter to the arrow function will be correctly inferred:
tabs.forEach(tab => {
tab.addEventListener("click", setActiveClass);
});
const setActiveClass = (event: Event) => {
tabs.forEach(tab => {
tab.classList.remove("active");
});
if(event.currentTarget != null){
(event.currentTarget as Element).classList.add("active");
}
};

Unable to use inject without observer

I'm currently using typed React (TSX) and mobx for state management.
I am able to build a component which uses both observer and inject decorators. But I am not able to build a component which uses inject without observer.
This passes the typescript compiler
export const DealershipBreadCrumb = inject("appStore")(observer((props: Props) => {
const {appStore} = props;
const dealership = appStore.getSelectedDealership();
return (
<div className="filter__container filter__group">
<a className="filter__link" href={`cars?q=${appStore.searchResults.searchQuery}`}>
<span className="filter__text">{dealership.name}</span>
</a>
</div>
)
}))
However this fails
export const DealershipBreadCrumb = inject("appStore")((props: Props) => {
With the following error message
[ts] Argument of type '(props: Props) => Element' is not assignable to parameter of type 'ComponentClass<{}>'.
Type '(props: Props) => Element' provides no match for the signature 'new (props?: {}, context?: any): Component<{}, ComponentState>'
Please assist me in making heads and tails of this error message. My bet is that the typings are out of date or something. Or otherwise the use of inject without observer is actually an invalid combination.
I think that your error is coming from this:((props: Props)...
when using inject that way I believe it is expecting a function, try use it like this:
export const DealershipBreadCrumb = inject('SystemDataStore')((props => {
btw don't forget that if you want to use inject without observer it will inject the last values of that store but it wont be aware of any change of values.

Resources