Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type error not indicated when spreading into a JSX component whose props type is defined as interface #43760

Open
wereHamster opened this issue Apr 21, 2021 · 11 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@wereHamster
Copy link

wereHamster commented Apr 21, 2021

Bug Report

πŸ”Ž Search Terms

react, component props, interface type, spread

πŸ•— Version & Regression Information

Bug present in 3.3.3 (oldest version available on Playground), as well as Nightly.

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

import * as React from 'react'

// Change interface to a type declaration and the bug disappears.
interface Props {}
// type Props = {}

// Change 'Props' => '{}' (ie. inline the type) and the bug disappears.
function Component(_: Props) { 
    return <div>C2</div>
}

export function Bug() {
    const props = { opt: "1" } as const

    // Should be a type error, since Component doesn't accept 'opt' prop
    return <Component {...props} />
}

πŸ™ Actual behavior

No error is indicated.

Error is indicated correctly when interface is changed to a type declaration, or the type is inlined.

πŸ™‚ Expected behavior

TypeScript should indicate a type error since Component doesn't accept the 'opt' prop.

@MartinJohns
Copy link
Contributor

MartinJohns commented Apr 21, 2021

Interfaces are subject to declaration merging. Types and inline types are not. edit: While this statement is right, it is unrelated to this issue.

@wereHamster
Copy link
Author

Are you saying in my code snippet there is another Props interface defined somewhere which defines a opt field? If so, where? How do I find out?

@MartinJohns
Copy link
Contributor

MartinJohns commented Apr 21, 2021

I'm saying due to declaration merging a new property could be added at any time, that's why TypeScript does not report an error.

If you don't want this behavior, then you can use type instead of interface. edit: While this statement is right, it is unrelated to this issue.

@wereHamster
Copy link
Author

Wait, so TS allows extending private interfaces that are not exported from modules? And therefore it speculatively assumes that Props ~ any since it can be arbitrarily extended?

Also, why does TS indicate a type error in <Component opt="1" />? In both cases, we are passing a (at compile time) known prop to a component which (due tu declaration merging) accepts, well, basically anything. I'm confused.

@wereHamster
Copy link
Author

wereHamster commented Apr 21, 2021

Hm, TS treats an empty interface differently, somehow? If I add a dummy field to Props now suddenly TS reports an error:

import * as React from 'react'

interface Props {
    unused?: number
}

function Component(_: Props) { 
    return <div>C2</div>
}

export function Bug() {
    const props = { opt: "1" } as const
    return <Component {...props} /> // <- Type '{ opt: "1"; }' has no properties in common with type 'IntrinsicAttributes & Props'
}

@MartinJohns
Copy link
Contributor

MartinJohns commented Apr 21, 2021

I might be confusing the issue with Why are all types assignable to empty interfaces?, but I would have expected type aliases and inline types to behave the same. edit: This statement is unrelated to this issue.

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Apr 21, 2021
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Apr 21, 2021
@wereHamster
Copy link
Author

wereHamster commented Apr 21, 2021

Ok, I think my problem is not related to React/JSX but to interfaces, spread, and extra fields:

function f(_: { foo: number }) { 
    return 1;
}

function g(props: { bar?: string }) {
    f({ foo: 1 }) // TS: pass;
    f({ foo: 1, bar: "1" }) // TS: error; due to extra 'bar' prop
    f({ foo: 1, bar: "1", ...props }) // TS: pass; weird, I'd have expected a type error here!
    f({ foo: 1, bar: "1", baz: "1", ...props }) // TS: error; because extra 'baz' field, but 'bar' still silently accepted.
}

@RyanCavanaugh
Copy link
Member

There are a lot of subtle things here but this isn't supposed to be one of them.

@pamply
Copy link

pamply commented Apr 21, 2021

Facing a similar issue. Passing spread props isn't get catch. I'm surprised this is happening in old versions

Test in version 3.3.3

@Fi1osof
Copy link

Fi1osof commented Aug 12, 2021

My Playground with same error.
I think this is TS's bug.

type A = {
    test: number
}

const a: A = {
    test: 11,

    // Where Error?
    ...{
        sds: 23,
    },
    // OK: Error
    sds: 23,
}

const b: A = {
    test: 11,

    // Where Error?
    ...{
        sds: 23,
    },
}

const c: A = {
    test: 11,
    // OK: Error
    sds: 23,
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants