These three types usually confuse novice React developers. It seems like they are the same thing, just named differently.
But it's not quite right.
JSX.Element
vs ReactElement
Both types are the result of React.createElement()
/jsx()
function call.
They are both objects with:
- type
- props
- key
- a couple of other "hidden" properties, like ref, $$typeof, etc
ReactElement
ReactElement
type is the most basic of all. It's even defined in React source code using flow!
// ./packages/shared/ReactElementType.js
export type ReactElement = {|
$$typeof: any,
type: any,
key: any,
ref: any,
props: any,
// ReactFiber
_owner: any,
// __DEV__
_store: {validated: boolean, ...},
_self: React$Element<any>,
_shadowChildren: any,
_source: Source,
|};
This type is also defined in DefinitelyTyped package.
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;
}
JSX.Element
It's more generic type. The key difference is that props
and type
are typed as any
in JSX.Element
.
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> { }
// ...
}
}
This gives flexibility in how different libraries implement JSX.
For example, Preact has its own implementation with different API.
ReactNode
ReactNode
type is a different thing. It's not a return value of React.createElement()
/jsx()
function call.
const Component = () => {
// Here it's ReactElement
return <div>Hello world!</div>
}
// Here it's ReactNode
const Example = Component();
React node itself is a representation of the virtual DOM. So ReactNode
is the set of all possible return values of a component.
type ReactChild = ReactElement | ReactText;
type ReactFragment = {} | Iterable<ReactNode>;
interface ReactPortal extends ReactElement {
key: Key | null;
children: ReactNode;
}
type ReactNode =
| ReactChild
| ReactFragment
| ReactPortal
| boolean
| null
| undefined;
What to use for children
?
Generally speaking, ReactNode
is the correct way to type the children
prop. It gives the most flexibility while maintaining the proper type checking.
But it has a caveat, because ReactFragment
allows a {}
type.
const Item = ({ children }: { children: ReactNode }) => {
return <li>{children}</li>;
}
const App = () => {
return (
<ul>
// Run-time error here, objects are not valid children!
<Item>{{}}</Item>
</ul>
);
}
P.S. Follow me on Twitter for more content like this!
Learn from the best!
Where factory pattern is used in JavaScript?
JS is a multi-paradigm programming language, so it provides a lot of different ways to deal with the same problem.
👇
#javascript #FrontEnd #programming18:09 PM - 07 Feb 2022
Top comments (4)
When working with TypeScript and React, React already provides types for components with children so you don't have to dig into the type yourself. So instead of doing this:
You can just:
And if you want to get a little more crafty, you can use
JSX.IntrinsicElements
in your favor and "extend" native elements:And your
Item
component will get autocompletion for all the properties of ali
element. If you're working with an element that doesn't have children (likeinput
), you can useVFC
instead ofFC
, like this:Cheers!
But in React 18 intrinsic property of children won't work for
FC
from react.Wrote that way before I knew about the type changes in
FC
. You can still use it withIntrinsicElements
that has children on it, and for more custom elements you can usePropsWithChildren
.Check out React+Typescript Cheatsheets for more info.