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.
Solution 1:
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>
.
Solution 2:
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/
Solution 3:
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