Using Antd's message with typescript - reactjs

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)

Related

What is TS type for MUI Input styled component?

team!
Could you, please, give me a hand with this one?
So Mui documentation suggests setting a type for a Mui Styled component as follows:
const MyComponent = styled(MuiComponent)(({ theme }) => ({ // styling })) as typeof MuiComponent
That worked perfectly fine for Box, Container, Button and other components, however, once I did that with I got an error, stating that the type is wrong somehow:
Conversion of type 'StyledComponent<InputProps & { theme?: Theme | undefined; }, {}. {}>' to type '((props: InputProps) => Element) & {muiName: string; }' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Property 'muiName' is missing in type 'StyledComponent<InputProps & { theme?: Theme | undefined; }, {}. {}>' but required in type '{ muiName: string;}'
I tried using as typeof TextField and React.HTMLProps<HTMLInputElement> thinking that that should work , but nope, TextField gives the same error, and HTMLProps starts complaining about onChange, for some reason.
I've spent some time trying to find a way to resolve it on my own, but it's starting to consume too much time, for such a trivial task, hence I decided to seek assistance amongst those with more experience in TS than me.
Thanks in advance <3
I've tried and both snippets below worked for me:
const MyComponent = styled(Input)(({ theme }) => ({})) as typeof Input
const MyComponent2 = styled(TextField)(({ theme }) => ({})) as typeof TextField
I'm using
"#mui/material": "^5.8.0",
"#mui/styles": "^5.9.2",
It would be better if you could provide more code though.
The solution to your problem is actually described in the error. Typescript wants you to convert it to unknown first like so:
const MyComponent = (styled(Input)(({ theme }) => ({})) as unknown) as typeof Input
However, this is more of a hack to get things working, not the actual solution to your problem.

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.

React useCallback in typescript

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.

Type 'number[]' is not assignable to type '[ReactText, ReactText]'

I'm using Elastic UI component Dual range:
const [yearRangeVal, setYearRangeVal] = useState([1974, 2007]);
const onYearRangeChange = (value: <number[]>) => {
setYearRangeVal(value);
};
<EuiDualRange
min={1974}
max={2007}
showTicks
tickInterval={10}
onChange={onYearRangeChange}
value={yearRangeVal}
/>
And getting this error in VSCode:
No overload matches this call. Overload 2 of 2, '(props:
EuiDualRangeProps, context: any): EuiDualRange', gave the following
error.
Type '(value: <number[]>) => void' is not assignable to type '(values: [ReactText, ReactText], isValid: boolean,
event: ChangeEvent | MouseEvent<HTMLButtonElement,
MouseEvent> | KeyboardEvent<...>) => void'. Overload 2 of 2,
'(props: EuiDualRangeProps, context: any): EuiDualRange', gave the
following error.
Type 'number[]' is not assignable to type '[ReactText, ReactText]'.
This code is from Elastic UI library (see ValueMember variable):
type ValueMember = number | string;
export interface EuiDualRangeProps extends Omit<EuiRangeSliderProps, 'onChange' | 'onBlur' | 'onFocus' | 'value'> {
value: [ValueMember, ValueMember];
onBlur?: (event: React.FocusEvent<HTMLInputElement> | React.FocusEvent<HTMLDivElement>) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement> | React.FocusEvent<HTMLDivElement>) => void;
onChange: (values: [ValueMember, ValueMember], isValid: boolean, event: React.ChangeEvent<HTMLInputElement> | React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLInputElement>) => void;
Just can't unerstand what is ReactText ((JSX attribute) value: [React.ReactText, React.ReactText]) and why my types are not ok.
ReactText is an alias for string | number. It defines what types can be printed out as texts in React, which is both strings and numbers.
The EuiDualRange component is saying that its value must always be an array with two elements aka a Tuple type. Even though your state array is initialized with only two values, [1974, 2007], typescript assumes that it could be any length and would allow you to setState with a number array of any length. In other other words, the inferred type for useState is number[] but we want to limit it to only [number, number].
When you manually set the generic of useState with useState<[number, number]>([1974, 2007]) then the error on the property value goes away.
But we will still have an error on onChange, even if we set (value: [number, number]). This is because the EuiDualRange can accept string AND number and the typescript definition for its onChange callback says that the value it returns could be strings or numbers, so it expects your callback to accept both.
One way to make sure that your callback accepts the right types is to import the definition from the package, which is for the whole function rather than the props, and apply it to your callback.
import {EuiDualRange, EuiDualRangeProps} from "#elastic/eui";
const onYearRangeChange: EuiDualRangeProps['onChange'] = (value) => { /** ... **/ }
If we do that, our callback now accepts the right type of value, but we get an error further down when calling setState with that value because the value type is broader than our state type.
There are many ways to address this.
If we can be confident based on the behavior of the package that the component will always call onChange with the same type as what we gave it through value then we can basically tell typescript "ignore the error because I know more than you". You would write
const onYearRangeChange: EuiDualRangeProps['onChange'] = (value) => {
setYearRangeVal(value as [number, number]);
};
Where the "as" keyword is used to refine the type (type assertion). This is the simplest solution. Some people avoid this strategy because you open yourself up to potential errors if it turns out that you were wrong about what you were asserting.
Based on what I'm seeing from the EuiDualRange component, it is in fact returning numbers so I think it is fine to use "as" in this case. But I want to include an alternative because this might not be "best practice".
You can narrow the type by checking the returned values and making sure that they actually are numbers and not strings (type guards). This means that your code is doing extra work at runtime (though it's minuscule), but you get absolute certainty that your types are correct. In the case that types are wrong, you can do nothing or you could throw an error. In this example I'm destructuring the tuple to its individual elements for readability.
const onYearRangeChange: EuiDualRangeProps['onChange'] = ([min, max]) => {
if ( typeof min === "number" && typeof max === "number") {
setYearRangeVal([min, max]);
}
// optional else { }
};
If it's not important to your code whether the years are numbers or strings, you could allow your useState to accept both numbers and strings.
const [yearRangeVal, setYearRangeVal] = useState<[ReactText, ReactText]>([1974, 2007]);

Type error when passing a function to promise.then, but works fine when called with an inline function?

I'm adding redux-toolkit to an existing project that uses Redux, and I've run into some unexpected behavior with the Typescript typings. In the following code, I would expect passing unwrapResult to the .then would behave similarly to passing just (res) => unwrapResult(res).
import { unwrapResult } from '#reduxjs/toolkit';
import { useAppDispatch } from 'Init/store';
const pingAction = createAsyncThunk('test/ping', async (val: string) => {
return await fetch(`${Ufcw.API_URI}/api/values`).then((res) => res.text());
});
// Inside the component...
const dispatch = useAppDispatch();
const ping = () => {
dispatch(pingAction('ping'))
// This works fine...
.then((res) => unwrapResult(res))
.then((res) => console.log(res));
};
const ping2 = () => {
dispatch(pingAction('ping'))
// Typescript doesn't like this, but it runs fine at runtime
.then(unwrapResult)
.then((res) => console.log(res));
};
In the first example, the typing work properly, and the response type gets inferred correctly. In the second though, Typescript gives the following error on the .then(unwrapResult):
Argument of type '<R extends ActionTypesWithOptionalErrorAction>(returned: R) => PayloadForActionTypesExcludingErrorActions<R>' is not assignable to parameter of type '(value: PayloadAction<string, string, { arg: string; requestId: string; }, never> | PayloadAction<unknown, string, { arg: string; requestId: string; aborted: boolean; condition: boolean; }, SerializedError>) => string | PromiseLike<...>'.
Type 'unknown' is not assignable to type 'string | PromiseLike<string>'.
Property 'then' is missing in type '{}' but required in type 'PromiseLike<string>'.
In another project, it works fine, although the other project was written entirely with redux-toolkit. Both projects use the same version of Typescript, redux, react-redux, and redux-toolkit. In both projects, I'm also inferring the type of dispatch from the store:
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
I've also tried removing all the additional middleware that I have, and that didn't seem to make a difference.
Is there something obvious that I may be missing, or something I'm typing wrong? Thanks!
The first option works because you are defining a function which is promise like.
Reading your error it says that this type:
<R extends ActionTypesWithOptionalErrorAction>(returned: R) => PayloadForActionTypesExcludingErrorActions<R>
is not assignable to this type:
(value: PayloadAction<string, string, { arg: string; requestId: string; }, never> | PayloadAction<unknown, string, { arg: string; requestId: string; aborted: boolean; condition: boolean; }, SerializedError>) => string | PromiseLike<...>
Because....
Type 'unknown' is not assignable to type 'string | PromiseLike<string>'
So it looks like the return of unwrapResult does not conform to the return the promise is expecting because it wants a string | PromiseLike<string> and unwrapResult looks like it can return an unknown type per the error.
Looking at the source code for redux-toolkit:
/**
* #public
*/
export function unwrapResult<R extends ActionTypesWithOptionalErrorAction>(
returned: R
): PayloadForActionTypesExcludingErrorActions<R> {
if ('error' in returned) {
throw returned.error
}
return (returned as any).payload
}
It looks like the return type is PayloadForActionTypesExcludingErrorActions<R> as we were expecting from the first line of the error. So here is that return type.
type PayloadForActionTypesExcludingErrorActions<T> = T extends { error: any }
? never
: T extends { payload: infer P }
? P
: never
So as I read this, "If T extends {error: any} then the type is never." So this can never happen. Therefore the type is
" If T extends { payload: infer P }, it must be P, if it doesn't, well that'll never happen"
So I think the the return type is P.
So what is P? It's the type of T.payload. T was the R from unwrapResult. Can you check that type to see the type of R and if it has a payload in your environemnt?
Also we are calling dispatch here... I see useAppDispatch is of type store.dispatch. I'm guessing that store is an EnhancedStore from the documentation (I've never used redux) which accepts types <S, A>
export interface EnhancedStore<
S = any,
A extends Action = AnyAction,
M extends Middlewares<S> = Middlewares<S>
> extends Store<S, A> {
/**
* The `dispatch` method of your store, enhanced by all it's middlewares.
*
* #inheritdoc
*/
dispatch: DispatchForMiddlewares<M> & Dispatch<A>
}
So, I'm curious when you create your store or pass it data, are you perhaps missing a type declaration or letting it infer a type that could be unknown, particularly the type? That's based on this.
export interface Dispatch<A extends Action = AnyAction> {
<T extends A>(action: T, ...extraArgs: any[]): T
}
Then the action says "#template T the type of the action's type tag." which is being used above when extending the action.
/**
* An *action* is a plain object that represents an intention to change the
* state. Actions are the only way to get data into the store. Any data,
* whether from UI events, network callbacks, or other sources such as
* WebSockets needs to eventually be dispatched as actions.
*
* Actions must have a `type` field that indicates the type of action being
* performed. Types can be defined as constants and imported from another
* module. It's better to use strings for `type` than Symbols because strings
* are serializable.
*
* Other than `type`, the structure of an action object is really up to you.
* If you're interested, check out Flux Standard Action for recommendations on
* how actions should be constructed.
*
* #template T the type of the action's `type` tag.
*/
export interface Action<T = any> {
type: T
}
Would be easier if I had this open in an IDE. But I think the type you are passing when creating your store, or perhaps should be specifying, would clear this up.

Resources