I saw the following viewpoint on the React TypeScript Cheatsheet website: “An empty interface, {} and Object all represent “any non-nullish value”—not “an empty object” as you might think.”
For example:
let value: {};
value = 1;
value = "foo";
value = () => alert("foo");
value = {};
value = { foo: "bar" };
value = undefined; // Error Type
value = null; // Error Type
The example above shows that when the type is {}, you can match strings, functions, and any non-empty values.
So, as shown in the code below, when I define the type of the Child component as FC<{}>, my props type becomes {}, which seems to imply: “You can pass strings, functions, and any non-empty values as props to Child.”
However, when I use the Child component in the parent component and try to set the props of Child to { id: ‘1111’ }, it throws an error.
const Child:FC<{}> = (props) => {
return (
<div>
Child
</div>
)
}
const App = () => {
return (
{/**Error: Type '{ id: string; }' is not assignable to type 'IntrinsicAttributes'.**/}
<Child id={'111'}/>
);
}
However, the code below works fine.
let props: {}
props = {id: '111'}
I would like to receive a fundamental explanation.
==========================================================
I’ve come up with an answer that seems to convince myself, and I’m sharing it here for everyone to consider.
We know that when we write a <MyComponent/>
, it gets transpiled into createElement()
.
This is the declaration of createElement:
interface Attributes {
key?: Key | null | undefined;
}
function createElement<P extends {}>(
type: FunctionComponent<P>,
props?: Attributes & P | null,
...children: ReactNode[]
): FunctionComponentElement<P>;
Suppose we have a component:
const MyComponent = (props: {}) => <div>I am a child component</div>;
Originally, the props were non-null, but when passed to createElement, props became Attributes & P
, which is {key?: Key | null | undefined}
. At this point, props no longer represents a non-null value.
return (
<div className="App">
<MyComponent a="1" /> //error
<MyComponent key={"1"} /> //ok
</div>
);
```jsx
1
Things to note:
- React functional components are functions
- Functions in React are contravariant.
Quote from the blog:
Contravariance is a rule that enforces that a function’s input type
must be a supertype of the input type of the function it’s being
assigned to
Because of the above property of TypeScript:
The below will compile with an error:
type EmptyObject = {};
type Func = (f : EmptyObject) => void;
const func : Func = ({ id }) => {
console.log({id})
};
and this one will work without errors:
type IdObject = { id : string}
type Func2 = (f : IdObject) => void;
const func2 : Func2 = ({}) => {
};
And that is exactly the behaviour you see above.
Playground
When you define a React component with FC<{}>
, you’re saying that this component accepts NO props at all. This is because {}
is interpreted as the props object cannot contain any keys. So {}
means no keys.
The type {}
means that the props object is an empty object. When you try to pass { id: '111' }
, TypeScript raises an error because id
is not a key that {}
allows—since {}
is supposed to have no keys at all!
If you want a Child
component to accept any props, here is how you can do it:
const Child: FC<Record<string, unknown>> = (props) => {
return (
<div>
Child
</div>
);
}
Record<string, unknown>
is a TypeScript utility type that represents an object with string keys and values of any type. It is equivalent to { [key: string]: unknown }
. unknown
specifies that the values can be of any type, but their exact type is not known at compile time.
or if you want the Child to accept id
as a prop:
const Child: FC<{ id: string }> = ({ id }) => {
return (
<div>
Child with ID: {id}
</div>
);
}
10