Typescript adding "| null" to return type of Promise.all - reactjs

I have an async TS function that makes a request and casts the response data to a boolean and returns it, but in the calling function VS Code is telling me the return value is boolean | null when I make the call in Promise.all. Here's the code:
The function:
import apiAxios from "../apiAxios";
export default async function doesAssignmentHaveTakes(
assignmentId: number
): Promise<boolean> {
const response = await apiAxios.get(`/assignments/${assignmentId}/has-takes`);
return !!response.data;
}
And the caller:
import React, { FC, useState, useCallback } from "react";
import styled from "styled-components/macro";
import AssignmentForm, {
Props as AssignmentFormProps,
Value as AssignmentFormValue
} from "./AssignmentForm";
import useAsyncEffect from "../utils/useAsyncEffect";
import getAssignmentById from "../api/assignments/getAssignmentById";
import doesAssignmentHaveTakes from "../api/assignmentTakes/doesAssignmentHaveTakes";
interface Props extends AssignmentFormProps {
assignmentId: number;
onSubmit(value: Value): any;
}
export interface Value extends AssignmentFormValue {
assignmentId: number;
}
const EditAssignmentForm: FC<Props> = props => {
const { assignmentId, onSubmit, ...rest } = props;
const [showEditWarning, setShowEditWarning] = useState(false);
const [initialValue, setInitialValue] = useState<AssignmentFormValue | null>(
null
);
useAsyncEffect(
async isCancelled => {
const [fetchedAssignment, hasTakes] = await Promise.all([
getAssignmentById(assignmentId),
doesAssignmentHaveTakes(assignmentId)
]);
if (!fetchedAssignment) {
// TODO: Alert parent component?
return;
}
const value: Value = {
assignmentId: fetchedAssignment.id,
assignment: {
title: fetchedAssignment.title,
subTitle: fetchedAssignment.subTitle
},
sets: fetchedAssignment.sets
.map(set => ({
id: set.id,
index: set.index,
questions: set.questions
.map(question => ({
id: question.id,
index: question.index,
isPractice: question.isPractice,
questionText: question.questionText,
inputType: question.inputType,
questionImage: question.questionImage,
sampleResponseText: question.sampleResponseText,
sampleResponseImage: question.sampleResponseImage
}))
.sort((a, b) => a.index - b.index),
learningTarget: set.learningTarget,
isExampleCorrect: set.isExampleCorrect,
exampleImage: set.exampleImage,
character: set.character
}))
.sort((a, b) => a.index - b.index)
};
if (!isCancelled()) {
setInitialValue(value);
setShowEditWarning(hasTakes);
}
},
[assignmentId]
);
const handleSubmit = useCallback(
(value: AssignmentFormValue) => {
onSubmit({
...value,
assignmentId
});
},
[onSubmit, assignmentId]
);
if (!initialValue) {
// Loading...
return null;
}
return (
<AssignmentForm
{...rest}
initialValue={initialValue}
onSubmit={handleSubmit}
/>
);
};
export default styled(EditAssignmentForm)``;
The specific lines with the issue:
const [fetchedAssignment, hasTakes] = await Promise.all([
getAssignmentById(assignmentId),
doesAssignmentHaveTakes(assignmentId)
]);
And
setShowEditWarning(hasTakes);
The TS error:
TypeScript error in /Users/james/projects/math-by-example/client/src/components/EditAssignmentForm.tsx(71,28):
Argument of type 'boolean | null' is not assignable to parameter of type 'SetStateAction<boolean>'.
Type 'null' is not assignable to type 'SetStateAction<boolean>'. TS2345
69 | if (!isCancelled()) {
70 | setInitialValue(value);
> 71 | setShowEditWarning(hasTakes);
| ^
72 | }
73 | },
74 | [assignmentId]
And some screenshots of the error in VS Code
Why does TS add null to the resolved types of Promise.all?

The solution is to add as const to the array you pass to Promise.all.
Explanation
The problem is not with the typing of Promise.all or a bug in the compiler. The issue is what TypeScript does by default with an array. Consider this:
const q = [1, "a"];
The default type inference for q will be (string | number)[]. Even though you have a number as the first position, and a string as the second, TypeScript infers that all positions can be either a string or a number. If you want TypeScript to treat the array as a tuple and assign to each position the narrowest type possible, you can do:
const q = [1, "a"] as const;
TS will infer a type of readonly [1, "a"] for this array. So q can have only the number 1 in the first position and the string "a" in the second. (It is also readonly but that's a side issue here.) This was introduced in TypeScript 3.4.
Ok, what does this have to do with your case? When you pass your array to Promise.all TypeScript is using the kind of type inference I've shown in my first example. Promise.all sees an array in which each item can take the union of all values that the items can take. If you use as const then the inference will be like the second case I've shown above, and this will be reflected accordingly in the type that Promise.all gets. Again, there's no problem with Promise.all's typing. It works with what it gets: bad typing in, bad typing out.
Here's an illustration (also in the playground):
async function fa(): Promise<string | null> { return "foo"; }
async function fb(): Promise<boolean> { return true; }
async function main(): Promise<void> {
let a: string | null;
let b: boolean;
// Remove this "as const" and the code won't compile.
[a, b] = await Promise.all([fa(), fb()] as const);
console.log(a, b);
}
main();

This has been resolved from ts 3.9+ (release note), upgrade to 3.9 and you will not see this error.

The problem is with the type definitions for Promise.all, take a look. The return type of the .all always try to make a union of the array promises generic types.
Probably your other function getAssignmentById can return null, so the Promise.all will infer a return type of [something | null, boolean | null]. It is possibly a bug with the TS compiler, I am not sure. I made a playground with the same conditions to see the inference in practice, take a look at how it infers the generic types on Promise constructor, then remove the null from the return type of funcB and see the Promise.all type again... it behaves as expected.

Agree with Pedro's answer that Promise.all doesn't deal with different return types out of the box.
You can try declaring the return type of your Promise like this:
const [fetchedAssignment, hasTakes] = await Promise.all<string | null, boolean>([
getAssignmentById(assignmentId),
doesAssignmentHaveTakes(assignmentId)
]);

Related

How to use typescript generics constraints with useState

I am learning Typescript and have a custom React hook similar to this:
import { useEffect, useState } from 'react';
type TypeOne = {
one: string;
};
type TypeTwo = {
two: number;
};
type TypeThree = {
three: {
some: string;
};
}
type AnyPropertyWithString = {
[index: string]: string | AnyPropertyWithString;
};
export function getContent(condition: string): Promise<AnyPropertyWithString> {
return Promise.resolve({ one: 'content' });
}
export default function useContent<T extends AnyPropertyWithString>(
initial: T,
condition: string
): T {
const [content, setContent] = useState<T>(initial);
useEffect(() => {
async function fetchData() {
const data = await getContent(condition);
setContent(data);
}
fetchData();
}, [condition]);
return content;
}
My intention is to provide different types of data to this hook initially and save it in a state. Then fetch new data on some condition and replace the state. I want to restrict the type provided to the hook to AnyPropertyWithString. I want to use generic because the types I will provide could have different properties and multiple levels. The return type should be the same as generic.
I expect to use it like this:
const one: TypeOne = { one: 'content' };
const two: TypeTwo = { two: 2 };
const three: TypeThree = { three: { some: '3' } }
const firstResult = useContent(one, 'condition'); // ok
const secondResult = useContent(two, 'condition'); // expected TS error
const thirdResult = useContent(three, 'condition'); // ok
However, when I try to setContent(data), I have an error:
Argument of type 'AnyPropertyWithString' is not assignable to parameter of type 'SetStateAction<T>'.
Type 'AnyPropertyWithString' is not assignable to type 'T'.
'AnyPropertyWithString' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'AnyPropertyWithString'.
I don't want to cast it to T as I've read that this is a bad practice.
I suspect the problem is that getContent returns the data of the type that can't be effectively matched against, but I thought that generic constraint would narrow down T to AnyPropertyWithString (which is returned by getContent).
I tried useState<T | AnyPropertyWithString>, but then I have a problem with the return type. I also tried different combinations of extends, but none worked.
Could you, please, explain why I have this error and how I can work around it if that's possible?
I really appreciate any help you can provide.
How is getContent supposed to know what T is, and then return data in the format of T?
When you do this:
const hookReturnValue = useContent({ foo: string }, 'condition')
Then the T type in useContent is { foo: string }, which is a subtype of AnyPropertyWithString. getContent returns an entirely different subtype of T.
So if this code ran, then the initial value would set T to { foo: string }, then the effect would run and you would save { one: string } to state, which is not a compatible type.
Then your code would do:
const hookReturnValue = useContent({ foo: string }, 'condition')
hookReturnValue.foo.toUpperCase() // crash when effect completes
This is what is meant by this error:
'AnyPropertyWithString' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'AnyPropertyWithString'.
It's unclear how getContent would work, but it would need to know what kind of data format to return at runtime, and you aren't passing it anything that would let it figure that out.
So if you figure out how getContent will return the same type as the initial argument type of the hook, then the answer will start reveal itself.

How to solve Type 'HTMLCanvasElement' has no call signatures.ts(2349) for React?

I have TS errors in the code I am using for the react-canvas-confetti package. I keep on getting the following error when I am trying to define types for my function which you will see below.
(property) React.MutableRefObject<HTMLCanvasElement | null>.current:
HTMLCanvasElement This expression is not callable. Type
'HTMLCanvasElement' has no call signatures.ts(2349)
const refAnimationInstance = useRef<HTMLCanvasElement | null>(null)
const getInstance = useCallback((instance: any) => {
refAnimationInstance.current = instance
}, [])
const makeShot = useCallback((particleRatio: number, opts: any) => {
refAnimationInstance.current &&
refAnimationInstance.current({
...opts,
particleCount: Math.floor(200 * particleRatio),
})
}, [])
I cannot figure out how to define this so that refAnimationInstance does not throw an error here for .current:
refAnimationInstance.current({
...opts,
particleCount: Math.floor(200 * particleRatio),
})
You can see the working example of the confetti in this link here:
https://codesandbox.io/s/realistic-fn-react-canvas-confetti-forked-sixvv1?file=/src/App.js:904-975
It's pretty annoying that this library doesn't export their types, but you can still get it with this long convoluted usage of built-in types:
import { type IProps } from "react-canvas-confetti";
type CreateConfetti = NonNullable<Parameters<NonNullable<IProps["refConfetti"]>>[0]>;
Then use the type where you need it:
const refAnimationInstance = useRef<CreateConfetti | null>(null)
const getInstance = useCallback((instance: CreateConfetti) => {
refAnimationInstance.current = instance
}, []);
Also, you can use optional chaining to avoid having to use && short-circuiting:
refAnimationInstance.current?.({
...opts,
particleCount: Math.floor(200 * particleRatio),
})

Why are these tuple types conjoined?

I have recreated the error I'm getting in a brandnew Create-React-App repo, source code can be found here.
As you can see in MyContext.tsx, I'm simply adding the result of useState to my context. The type of the context is also defined as a tuple by
const UDContext = createContext<
| undefined
| [
UserData | undefined,
React.Dispatch<React.SetStateAction<UserData | undefined>>
]
>(undefined);
However, when I try to access the data in MyComponent.tsx by doing
const [userData] = useUserData();
I'm getting the error
Property 'username' does not exist on type 'UserData | Dispatch<SetStateAction<UserData | undefined>>'.
This to me looks like it is somehow combining the tuple's types to one?
The funny thing: the below code does actually work (it does give a warning that context may be undefined, which indeed makes sense).
const context = useUserData();
const userData = context[0];
Exactly the same is happening in MySetter.tsx, where the exact error is
Not all constituents of type 'UserData | Dispatch<SetStateAction<UserData | undefined>>' are callable.
The above "solution" without direct destructuring works here as well.
The only way I can get this to work, is by explicitly declaring the type of the return value of useUserData.
A few sidenotes:
This is the #next version of Create-React-App
I had to enable --downlevelIteration, or else the destructuring of useUserData() was giving Type '[UserData | undefined, Dispatch<SetStateAction<UserData | undefined>>] | undefined' is not an array type or a string type. Use compiler option '--downlevelIteration' to allow iterating of iterators.
I have a working example in CodeSandbox that gives no issues at all.
This seems to be a BUG in typescript and I found two open bugs with similar issues BUG 31613 and BUG 40215
Apart from the option you mentioned in question (explicit type on return), another option would be to not use tuple destructuring and for your code it would look like:
MyComponent.tsx
import React from "react";
import { useUserData } from "./MyContext";
const MyComponent = () => {
const userData = useUserData();
if (!userData) {
return <div>Loading username</div>;
}
return <div>{userData[0]?.username}</div>;
};
export default MyComponent;
MySetter.tsx
import { useEffect } from "react";
import { useUserData } from "./MyContext";
const MySetter = () => {
const userData = useUserData();
// Fakes an API call
useEffect(() => {
setTimeout(() => {
userData && userData[1]({ username: "FooBar" });
}, 750);
});
return null;
};
export default MySetter;
That's because your context can be also undefined so you need to make checks to make sure you always return something from your context. You could create reusable method for creating context thats always something, and not undefined:
function createCtx<T>() {
const Context = React.createContext<T | undefined>(undefined);
const useCtx = () => {
const ctx = React.useContext(Context);
if (typeof ctx === "undefined") {
throw new Error("useContext must be inside provider with a value.");
}
return ctx;
};
return [useCtx, Context.Provider] as const;
};
Then use this method as such where you'd probably export useUserData and UserDataContext
const [useUserData, UserDataProvider] = createCtx<[
UserData | undefined,
React.Dispatch<React.SetStateAction<UserData | undefined>>
]>();
const UserDataContext: React.FC = ({ children }) => {
const [userData, setUserData] = React.useState<UserData>();
return (
<UserDataProvider value={[userData, setUserData]}>
{children}
</UserDataProvider>
);
};
and use it as such in your components
export default function App() {
const [userData] = useUserData();
return (
<h2>{userData?.username}</h2>
);
}
Not only does this solve your problem, but also gives you reusable method that helps you with other things that you'll surely need.
Cheers.

Typescript: how to declare a type that includes all types extending a common type?

TLDR: Is there a way in Typescript to declare a type that encompasses all types that extend a given interface?
My specific problem
I am writing a custom React hook that encapsulates logic for deciding whether or not an element is moused over. It is modelled roughly after this hook. It exposes a ref that should be able to take any HTMLElement:
const ref = useRef<HTMLElement>(null);
The problem is, if I try to use this ref on any specific React element, I get an error telling me that this specific element is not quite HTMLElement. For example, if I use it with HTMLDivElement, I get this error: argument of type HTMLElement is not assignable to parameter of type HTMLDivElement.
Here's a simple repro case of the problem above in Typescript playground
Obviously, I wouldn't want to list types of all html elements in my hook. Given that HTMLDivElement extends the HTMLElement type, is there a way of declaring that the type that I am actually after is not strictly HTMLElement, but whatever extends HTMLElement?
React code example
source code of the hook
import { useRef, useState, useEffect } from 'react';
type UseHoverType = [React.RefObject<HTMLElement>, boolean];
export default function useHover(): UseHoverType {
const [isHovering, setIsHovering] = useState(false);
let isTouched = false;
const ref = useRef<HTMLElement>(null); // <-- What should the type be here?
const handleMouseEnter = () => {
if (!isTouched) {
setIsHovering(true);
}
isTouched = false;
};
const handleMouseLeave = () => {
setIsHovering(false);
};
const handleTouch = () => {
isTouched = true;
};
useEffect(() => {
const element = ref.current;
if (element) {
element.addEventListener('mouseenter', handleMouseEnter);
element.addEventListener('mouseleave', handleMouseLeave);
element.addEventListener('touchstart', handleTouch);
return () => {
element.removeEventListener('mouseenter', handleMouseEnter);
element.removeEventListener('mouseleave', handleMouseLeave);
element.removeEventListener('touchend', handleTouch);
};
}
}, [ref.current]);
return [ref, isHovering];
}
which produces type error if used like this:
import useHover from 'path-to-useHover';
const testFunction = () => {
const [hoverRef, isHovered] = useHover();
return (
<div
ref={hoverRef}
>
Stuff
</div>
);
}
Type error in example above will be:
Type 'RefObject<HTMLElement>' is not assignable to type 'string | RefObject<HTMLDivElement> | ((instance: HTMLDivElement | null) => void) | null | undefined'.
Type 'RefObject<HTMLElement>' is not assignable to type 'RefObject<HTMLDivElement>'.
Property 'align' is missing in type 'HTMLElement' but required in type 'HTMLDivElement'.
I think you are mistaken about the direction of the assignment that fails. If you have an interface A, then the type that matches all subclasses of A is just called A. This way, HTMLElement (i.e. is assignable from) any HTML element, e.g. HTMLDivElement.
This means that if you have a bunch of functions, one of them accepts HTMLDivElement, another accepts HTMLLinkElement etc, then there is no real type that you can pass to all of them. It would mean you expect to have an element that is both a div and a link and more.
Edited based on your edits of the question:
If the code you have works fine, and your only problem is that it doesn't compile, then just make your useHover generic, like this:
type UseHoverType<T extends HTMLElement> = [React.RefObject<T>, boolean];
function useHover<T extends HTMLElement>(): UseHoverType<T> {
const ref = useRef<T>(null); // <-- What should the type be here?
...
And then:
const testFunction = () => {
const [hoverRef, isHovered] = useHover<HTMLDivElement>();
Something like this will make your code compile fine, without changing its runtime behaviour. I'm unable to tell if the runtime behaviour right now is as desired.
It works as expected, since HTMLDivElement extends HTMLElement. In your typescirpt playground you mixed it up. I updated it by switching x and y in this playground. You want the function to extend HTMLElement and pass y, which is and HTMLDivElement into it. And that works.

Cannot invoke an object which is possibly 'undefined'.ts(2722)

I have a button component. I simply pass it just one onClick prop out of many optional props I've defined:
const Button = (props: ButtonProps) => {
const handleClick: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> = e => {
props.onClick(e);
}
return (
<StyledButton onClick={handleClick}>
{props.children}
</StyledButton>
);
};
Then I'm using it like this:
<Button onClick={(e) => {
console.log(e);
}}>Click me!</Button>
Now how can as per the error mentioned in question, object be possibly undefined? I'm clearly passing the function to it and that too as per the type definition. So, I'm passing an object to it. Simple enough!
...
onClick?: React.MouseEventHandler<HTMLElement>
...
I've added a few more strict checks in this project recently and relevant one's are:
"strictFunctionTypes": true,
"strictNullChecks": true
strict:true being already present, this error never occurred.
What's the issue here?
Update - Types added
export interface IBaseButtonProps {
type?: ButtonType;
disabled?: boolean;
size?: ButtonSize;
block?: boolean;
loading?: boolean | { delay?: number };
icon?: string;
className?: string;
prefixCls?: string;
children?: React.ReactNode;
}
export type AnchorButtonProps = {
href: string,
target?: string,
onClick: React.MouseEventHandler<HTMLElement>
} & IBaseButtonProps & Omit<React.AnchorHTMLAttributes<any>, 'type' | 'onClick'>;
export type NativeButtonProps = {
onClick: React.MouseEventHandler<HTMLElement>,
htmlType?: ButtonHTMLType
} & IBaseButtonProps & Omit<React.ButtonHTMLAttributes<any>, 'type' | 'onClick'>;
export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>
Notes:
The possible solution is to either destructure the props and add the default prop. Or use defaultProps from React. But not sure if I should require that really with Typescript.
With Typescript 3.7+, you can also use optional chaining to invoke the optional prop method:
const Button = (props: ButtonProps) => {
const handleClick: React.MouseEventHandler<
HTMLButtonElement | HTMLAnchorElement
> = e => {
props.onClick?.(e); // works
};
};
You can read more about using optional chaining - https://www.stefanjudis.com/today-i-learned/optional-chaining-helps-to-avoid-undefined-is-not-a-function-exceptions/
Now how can as per the erro mentioned in question, object be possibly undefined? [sic]
The use of Partial<T> around export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps> causes onClick to be optional. When we use Partial<T>, all the properties receive the ? and thus become optional, which means that all of them can be undefined.
There are two approached to a fix: one is to keep ButtonProps the same with onClick as optional, and to check that onClick is defined before calling it (fix 1); the other is to change ButtonProps to make onClick required (fix 2 and 3).
Fix 1: onClick remains optional
Use the ButtonProps that you already have, and then check that onClick is defined before calling it. This is what antd does in the code you linked in the comments.
const Button = (props: ButtonProps) => {
const handleClick: React.MouseEventHandler<
HTMLButtonElement | HTMLAnchorElement
> = e => {
if (props.onClick) props.onClick(e); // works
};
};
Fix 2: onClick becomes required
Change ButtonProps by not applying the Partial to the NativeButtonProps:
type ButtonProps1 = Partial<AnchorButtonProps> & NativeButtonProps;
const Button1 = (props: ButtonProps1) => {
const handleClick: React.MouseEventHandler<
HTMLButtonElement | HTMLAnchorElement
> = e => {
props.onClick(e); // works
};
};
Fix 3: onClick becomes required too
Define a RequireKeys type, which lets you to specify the keys that are not optional.
type RequireKeys<T, TNames extends keyof T> = T &
{ [P in keyof T]-?: P extends TNames ? T[P] : never };
type ButtonProps2 = RequireKeys<ButtonProps, "onClick">;
const Button2 = (props: ButtonProps2) => {
const handleClick: React.MouseEventHandler<
HTMLButtonElement | HTMLAnchorElement
> = e => {
props.onClick(e); // works
};
};
The answers to Mapped Types: removing optional modifier have more information about how I defined RequireKeys<T>.
Just a clear cut answer
if (props.onClick) props.onClick(e);
if you are defining a function props and want it to be optional, define it as,
export type ButtonProps = {
function?: () => void;
};
Explanation:
If you want to use a function as props, there may be instances when you want to pass that function (as props) and there may be other instances where you don't want to pass it.
for example,
Common Code WHERE calling the <Home/> component, say index.ts/index.js
function myfunction(){
//do something
alert("hello")
}
return (
<>
<Home myfunction={myfunction}/> //passing prop
<Home/> // not passing
</>
)
In JS, home.js
export default function Home({myfunction}) {
const const1 = "Hello World"
return (
//do something
myfunction(); //IMPORTANT line
)
}
Now, its almost equivalent in TS, home.ts
In TS, we define types of everything. So, in that case we have to define type of this function myfunction also, that we are passing.
So, for this function, we realise that,
It recieves no params, so () (empty parenthesis) is enough, if any params are there, we need to define types for them also.
Returns nothing, so return type void
export type HomeProps = {
myfunction?: () => void;
};
export default function Home({ myfunction }: HomeProps) {
const const1 = "Hello World"
return (
//do something
if (myfunction) myfunction(); //IMPORTANT line
)
}
Hint: above answer
I was creating my own custom MUI button and this is how I did it.
interface ButtonTypes {
handleClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
}
On the button component
<LoadingButton
onClick={(e) =>handleClick && handleClick(e)}
>
{"Send"}
</LoadingButton>
If you're using VScode, hover on the OnClick property and you should see the expected types.
The logical operator AND (&&) returns either the value of the first falsy operand it finds or the value of the last operand if all values are truthy (Source). Therefore, we can simply write:
(props.onClick && props.onClick(e));
The above solution is too much confusing I was getting the error
Cannot invoke an object which is possibly 'undefined
It can be easily solved by removing ? from MyProps
For Example
type CounterProps = {
value : number,
selected? : boolean,
onDelete? :(id : number) => void,
//remove ? from onDelete to solve the problem
id? : any,
}
// bottom line will shows Cannot invoke an object which is possibly undefined
<button onClick={() => this.props.onDelete(this.props.id)}>Delete</button>
A good explanation is provided here
The best variant is to use ?.call(this: unknown, ...args: any[]) or ?.apply(this: unknown, args: any[]) methods
So, lets imagine we have next declarations
type callback = ((x: number, y: number) => number) | null;
let a: callback;
let b: callback;
a = (x, y) => x + y; // it works with arrow functions
b = function (x, y) { // and normal functions
return x + y;
};
function x(cb1: callback, cb2: callback) {
console.log(cb1?.call(0, 5, 6)); // in this case you
console.log(cb2?.call(0, 5, 6)); // cant invoke cb1() or cb2()
console.log(cb1?.apply(0, [5, 6])); // but you can use call or apply
console.log(cb2?.apply(0, [5, 6])); // where first parameter can be any value
}
x(a, b); // 11 11 11 11
class C {
public f?: callback;
public setF() {
this.f = (x, y) => {
return x + y;
};
}
}
const c = new C(); // same with objects
c.setF();
console.log(c?.f?.call(c, 2, 3)); // 5
For any one who come next. Another option is to use type casting.
like:
props = props as NativeProps
In my experience I used a context who return a Partial type object and i needed to do type casting to overcome the undefined error. like:
const {setSomething} = useContext(SomePartialContext) as MyContextType
This is the syntax that helped solve this issue for me.
Different ways of getting a solution:
search.prop('onSearch')!('text' as unknown)
search.prop('onSearch')!({} as unknown)
search.prop('onSearch')!({} as any)
The key portion is: !({} as any}

Resources