As React developer, we are all familiar with creating components, whether as classes or functions, to add features to web pages.
One of the common cases in React is a component that has JSX Children. When you want to nest content inside a JSX tag, the parent component will receive that content in a prop called children
.
For instance, the Modal component below will receive children
prop set to and render it in a wrapper div.
// React JavaScript
function Modal(props) {
const { children } = props;
return <div className="modal">{children}</div>;
}
function Page() {
return (
<Modal>
<title>Submit Form</title>
<form onSubmit={handleSubmit}>
<input type="text" />
<input type="submit" />
</form>
</Modal>
);
}
If it wants to be converted to TypeScript, most of developers use PropsWithChildren
utility type from @types/react
package.
// React TypeScript
import type { PropsWithChildren } from "react";
// props: {
// children?: React.ReactNode;
// }
function Modal(props: PropsWithChildren<{}>) {
const { children } = props;
return <div className="modal">{children}</div>;
}
function Page() {
return (
<Modal>
<title>Submit Form</title>
<form onSubmit={handleSubmit}>
<input type="text" />
<input type="submit" />
</form>
</Modal>
);
}
It helps TypeScript understand that the component should have the children
prop.
Problems
Sounds good so far, right? But here's where things get weird.
Let me highlight the code.
// props: {
// children?: React.ReactNode; ❌
// }
// Unexpected ❌
import type { PropsWithChildren } from "react";
// props: {
// children?: React.ReactNode;
// }
function Modal(props: PropsWithChildren<{}>) {
const { children } = props;
return <div className="card">{children}</div>;
}
function Page() {
return <Modal />;
}
The children
is supposed to be required. That means we must provide children
when using Modal
component. But, guess what? It becomes optional! Rendering Modal
component without children
is now even possible 😨.
Let's check what's inside PropsWithChildren
. Here it is referring to @types/react Github.
type PropsWithChildren<P = unknown> = P & { children?: ReactNode | undefined };
Turns out the culprit is @types/react
itself 😵.
This little bug might not seem like a big deal at first, but trust me, it can cause some serious problems and mess up with your business logic. And the worst part? Neither React nor TypeScript will show any errors, leaving you scratching your head when things go wrong 😵💫.
Solutions
So, what can we do about this? Let's explore some solutions!
1. Never Trust and Always Check "third-party" code
As you know earlier, the culprit is @types/react
itself. This is not the first time. Previously, developers had a problem with React.FC
. Some of them even posted about it.
TypeScript + React: Why I don't use React.FC
Why you probably shouldn’t use React.FC to type your React components
Why You Should Probably Think Twice About Using React.FC
All those fancy "magic" codes will eventually lose their "magic" over time. So, you should always check what's behind the code.
2. Create your own Utility type
Since the PropsWithChildren
from @types/react
is not that hard, why don't you create it yourself?
type PropsWithChildren<P = unknown> = P & { children: ReactNode };
PropsWithChildren
utility type is for components that have children
prop and it's required.
type PropsWithOptionalChildren<P = unknown> = P & { children?: ReactNode };
PropsWithOptionalChildren
utility type is for components that have children
prop, but it's optional.
It looks reliable and unambiguous. ✅
Conclusion
Remember these tips and you'll save yourself from headaches. I hope these tips come in handy for your projects! ✨