Seems to me that everything is working as intended. TypeScript's structural type system will only check that required properties exist, not that extra properties don't:
type Foo = { bar: string }
function doSomething(thing: Foo) { … }
// Not explicitly a `Foo`
const foo = { bar: "barbarbar", baz: "bazbazbaz" }
// This is allowed since the type of `foo` extends `Foo`
doSomething(foo);
Something that might be confusing is TypeScript's concept of freshness, also known as strict object literal checking. With freshness checks TypeScript will in fact check that object literals only define known properties:
type Foo = { bar: string }
function doSomething(thing: Foo) { … }
// Allowed, only known properties defined:
const aFoo: Foo = { bar: "barbarbar" }
// This is also, by necessity, allowed:
doSomething({ bar: "barbarbar" })
// Not allowed with strict object literal checking:
const bFoo: Foo = { bar: "barbarbar", baz: "baz" }
// ~~~~~~~~~~
// Type '{ bar: string; baz: string; }' is not assignable to type 'Foo'.
// Object literal may only specify known properties, and 'baz' does not exist in type 'Foo'.
// (2322)
// Also not allowed:
doSomething({ bar: "barbarbar", baz: "baz" })
// ~~~~~~~~~~
// Argument of type '{ bar: string; baz: string; }' is not assignable to parameter of type 'Foo'.
// Object literal may only specify known properties, and 'baz' does not exist in type 'Foo'.
// (2345)
What's confusing here is the separation of known and "extra" properties. With type unions, there might be properties that are known but extra:
type A = { a: string }
type B = { b: string }
type U = A | B
// Both allowed:
const aFoo: U = { a: "foo" }
const bFoo: U = { b: "bar" }
// But so is this, as all properties are known ones,
// even though one of them is not strictly needed to fit the shape of the type:
const cFoo: U = { a: "foo", b: "bar" }
// But this will fail with the extra property `c`
const dFoo: U = { a: "foo", b: "bar", c: "baz" }
// ~~~~~~~~
// Type '{ a: string; b: string; c: string; }' is not assignable to type 'U'.
// Object literal may only specify known properties, and 'c' does not exist in type 'U'.
// (2322)
What your problem boils down to is that TypeScript cannot guarantee that the property you're trying to destructure actually exists:
type A = { a: string }
type B = { b: string }
type U = A | B
// This is allowed, since all properties are known
const foo: U = { a: "foo", b: "bar" }
// These fail, since the type information cannot guarantee the `a` or `b` properties exist on U:
// By the definition of U, `foo` is guaranteed to have at least all the properties of either `A` or `B`,
// but if `foo` extends `B`, it's not guaranteed to have `a` and vice versa
const { a } = foo;
const { b } = foo;
But when the type system can guarantee that a property exists in all the parts of the union, you can safely destructure that property:
type A = { a: string, x: number }
type B = { b: string, x: number }
type U = A | B
const foo: U = { a: "foo", b: "bar", x: 0 }
// These fail as per above
const { a } = foo;
const { b } = foo;
// This will work, as `x` is guaranteed to exist on all values of type `U`
const { x } = foo;
Now to look at your exact problem, TypeScript is telling you what the problem is, although a bit too verbosely:
TS2339: Property 'to' does not exist on type 'PropsWithChildren({ onClick: MouseEventHandler ; } | { to: string; }) & { buttonComponent?: ({ onClick: MouseEventHandler }: { onClick: any; }) => Element; } & BoxProps>'.
As StickyButton is a FC<Props>, by the definition of FC<Props>, it is a function that accepts an argument of type Props. In your case, type Props equals the word salad after "does not exist on type" in the error message, and TypeScript cannot guarantee that a property called to will always exist in it. Thus it cannot be destructured in the function argument:
const StickyButton: React.FC<…> = ({
children,
onClick,
to, // This might not exist
buttonComponent: ButtonComponent = Button,
...props
}) => { … }
React.FC? I tried pulling that out and it's not giving me any errors.toin the function body is an error, and thatfoois allowed to have bothtoandonClick