I expected it works:
interface Point {
x: number;
y: number;
}
function A({x, y}: Point = {x: 1, y: 1}) {
return `x:${x}y:${y}`;
}
export default function App() {
return <A/>; //error TS2739
}
export default function App() {
return <>{A()}</>; //ok, no problem
}
Why it doesn’t work?
ps.
Stackoverflow ask me to add some details, but I can’t do that
because code explains problem well.
That is why I add this ps.
2
Answers
The problem likely appears due to how default parameters and destructuring work in React.
When you call your function
A()
without arguments, it uses the default parameter.However when you use
<A />
, React passes an empty props object "{}" to A.Function A then receives this empty object and tries to destructure x and y from it.
But because x and y are missing from the props object, their value will be
undefined
.To solve your issue either you pass a value to your component
Or set x and y as optional in the interface
and then handle the undefined case for each property inside your "A" component function.
Lastly you need to return a valid jsx element and not a string.
Like this:
Of course it is not required to be a React fragment
When you write
<A />
, it ends up callingA({})
with an empty object as argument; it does not callA()
with no argument. So you need to writeA
such that it accepts an empty object and does what you want with it. In the comments you implied that you wanted to either require bothx
andy
, or you want to prohibit bothx
andy
. You don’t want to makex
andy
independently optional.If so then you need something like
Note that TypeScript doesn’t have a type that means "empty object" the way we’re talking about it. If you write
{}
likePoint | {}
then it will accept anything at all, since{}
means "a type with no known properties" which isn’t the same as "a type known not to have properties". So instead we can use{x?: never, y?: never}
to mean a type wherex
andy
are either missing orundefined
. My littleProhibitKeysOf<T>
utility type is just there to show how to extend this to more properties if needed.Note that there’s no point in giving
A
a default argument like{x: 1, y: 1}
, becauseA
will never be called without an argument. Instead, you want something like default destructured properties, like{x = 1, y = 1}
above, so that when you pass in{}
,x
andy
get the default properties of1
. This would technically mean if you pass in{x: 2}
you’d getx
is2
andy
is1
, which you don’t want… but that possibility is precluded by the annotationPoint | EmptyObj
.And now you can call
A
like:where you can pass in either both
x
andy
or neitherx
nory
, and if you try passing just one then you get an error.Playground link to code