

Discover more from Developreneur
In the past few weeks, I've been knee-deep in refactoring a many-many years old TypeScript React + Prisma codebase.
It turned out that TypeScript was often just used to avoid errors in the IDE. Bummer. This defeats the purpose of TypeScript. Here, look:
interface ButtonProps {
className: string;
children: any;
onClick: Function;
}
export const Button = (props): ButtonProps => ...
I’ll show you how to actually type a React component to get the benefits of TypeScript:
Children
It was a long discussion of how to type children
. Just adding React.FC
to the component, using any
, typing as ReactNode
, etc.
With React 18, you must list children
explicitly and have it typed as React.ReactNode
. Typing the component as React.FC
or FunctionComponent
is not required anymore!
export const Button = (props: { children: React.ReactNode }) => {
return <button className="base-classname">{props.children}</button>;
};
Event Handler and className
Well, despite how unrelated are these two, we still need to consider both at the same time to understand how the included DOM typings (event and element) are working.
Here’s the current status:
interface ButtonProps {
className: string;
children: React.ReactNode;
onClick: Function;
}
We know that the onClick
is going to be an event, not just a simple function. We also know that we’re rendering a <button>
HTML element inside our React component. Looking at the included DOM types and React typings, we can see that the <button>
is a HTMLButtonElement
, and onClick
is a MouseEventHandler
.
Let’s update our interface and mark the optional types.
interface ButtonProps {
className?: string;
children: React.ReactNode;
onClick?: MouseEventHandler<HTMLButtonElement>;
}
We used the MouseEventHandler
type from React and added it to our ButtonProps
. That’s not so future-proof, right? What if we want to include more props for the HTML button?
Making it 100x better: Mirroring all props of an HTML element
ComponentPropsWithoutRef
to the rescue. This is a helper type included in React, so by adding a generic, we can access all of the HTML and React props related to the HTML element.
It also takes care of optional props and default values.
Here’s our component using ComponentPropsWithoutRef
(Note: clsx is a handy package to merge multiple classes.):
import { ComponentPropsWithoutRef } from "react";
import clsx from "clsx";
export const Button = ({ className, children, ...rest }: ComponentPropsWithoutRef<"button">) => (
<button {...rest} className={clsx("base-classname", className)}>
{children}
</button>
);
What if we want a unique prop?
You probably noticed that by using ComponentPropsWithoutRef
, the component has access to all the React component props also. Aka. the children
prop is also typed. So we only need to extend typings if we have a unique prop.
So, let’s assume we make our Button component capable of rendering 1 of 2 variants.
We need a default and a ghost variant. It's only the applied classnames that differ. We don't want the user to type default all the time, so make the default variant the default value (lol).
Here’s our final Button component. Fully typed with 2 variants.
import { ComponentPropsWithoutRef } from "react";
import clsx from "clsx";
interface ButtonProps extends ComponentPropsWithoutRef<"button"> {
variant?: "default" | "ghost";
}
export const Button = ({ className, children, variant = "default", ...rest }: ButtonProps) => (
<button
{...rest}
className={clsx("base-classname", variant === "ghost" ? "ghost-classname" : "default-classname", className)}
>
{children}
</button>
);
Looks much simpler and easier without the need to type events and others manually, right?
Do you have other tips & tricks about using React with TS? Let me know in a reply.
❓ Weekly Quiz
Last question: Which of the below is NOT a business personality of a developreneur?
Answer: Salesman
Curated
👉 4 Developreneur Tools
Realtime Colors - Try out different color schemes live on the website. Then copy-paste it to your project. And it comes from a fellow solopreneur, who originally built this tool for her own usage but later decided to publish it.
Blocknote - Open-source block-based text editor for React. Similar to Notion, based on Tiptap. (Which actually also just released a stable version, so you can procrastinate by integrating text editors.)
Notion Projects - The project management tool we need but didn’t deserve. Native project-management in Notion. Automation, sprints, kanban, deadlines, and obviously AI. Let me know if you’ve tried it out.
useHooks - Updated collection of React hooks. Safe to use for server components and React 18. Built by the team at ui.dev. I would love this if it’d be copy-paste instead of another dependency. But hey, it’s open source, so I can just YOINK the code.
👉 Content to binge
UI Dev Newsletter - Level up your front-of-the-frontend development with UI Dev Newsletter - a hand-curated newsletter delivering articles, tutorials, opinions, and tools to your inbox every two weeks.
A Visual Guide to Next 13 App Router - The app router is still hot & fresh. Still a daily topic on tech Twitter. This article is an awesome guide to understanding how to write code with it.
17 big & little things ▷ - WWDC happened earlier this month. Here’s a short video going over the small announcements and Vision Pro.
👉 That’s about it
Thank you so much for reading my email! I’d be glad to hear from you if you have any questions, feedback, or comments.
If you enjoyed this post and want more, just below, you’ll find my social media profiles, where I post daily something educational, or something completely pointless.
Please forward this mail to your friends who might benefit from it. Get your subscription here if it was forwarded to you.
Social Media: Twitter, YouTube, Instagram, TikTok, LinkedIn
Until next time,
David