DEV Community

Cover image for Deprecating a React component using TypeScript Overload
Matti Bar-Zeev
Matti Bar-Zeev

Posted on

Deprecating a React component using TypeScript Overload

A disclaimer -
Please be advised that the solution presented in this article addresses a very specific problem/situation, and it is not the recommended way to support component versions within a project.

Situation? What situation?

Say that you have a monorepo and in this monorepo you have a package which contains the common components the different repos use. All consumers are using “workspace:^” for this package and since the component package is not published to a registry the publish-time resolution is irrelevant, and workspace:^ simply means every package in the monorepo that depends on it always gets the current local implementation — live, not pinned to a registry snapshot.
But what happens when you would like to introduce a breaking change to a component, say change the Card component’s border radius, title bg color and shadow? This means that all consumers can potentially break, right?
So some might say to maybe create a new component and mark the old one as deprecated, but this would mean that all consumers will need to change the component name they are using and it also means that you will now have 2 different component, residing in 2 different dirs (if you are well organized) and it creates even more “noise”.

I wanted something simpler and more sustainable. My goals are:

  • Keep the same component name
  • Be able to see what is deprecated
  • Have an easy way to switch between the old and the new

Here is one way, I came up with of, doing that

TypeScript, among other languages like Java, has a really neat concept of Overloading. Overloading means you can have the same name for, say, a function but if you call it with different arguments you get a different implementation, and we all know that React components are basically functions, right? great.

Say I have a very simple Card component:

import React from 'react';
import './index.scss';


export interface CardProps {
   title: string;
   content: string;
   style?: React.CSSProperties;
}


const Card = ({title, content, style}: CardProps) => {
   return (
       <div className="card" style={style}>
           <div className="card-header">
               <span className="card-title">{title}</span>
           </div>
           <div className="card-content">{content}</div>
       </div>
   );
};


export default Card;
Enter fullscreen mode Exit fullscreen mode

And it looks like this:

But I would like to create a newer version of the Card component, which has different border radius, title bg color and shadow, and mark the old version as deprecated.
I can create an Overload for the Card function. Let’s see how:

I first created a new interface for the new card props. In it I have a single prop called “new” which is always true, and we will later see how we use it.
(you can go even further and have v1 if you plan to support more than a single version, but please don’t do that - this does not come to replace package versioning)

export interface NewCardProps extends CardProps {
   new: true;
}
Enter fullscreen mode Exit fullscreen mode

Next we call the old Card function “LegacyCard” and the new version “NewCard”. In the NewCard we do all the breaking changes we wanted.

function LegacyCard({title, content, style}: CardProps): React.JSX.Element {
   return (
       <div className="card" style={style}>
           <div className="card-header">
               <span className="card-title">{title}</span>
           </div>
           <div className="card-content">{content}</div>
       </div>
   );
}


const NewCard = ({title, content, style}: CardProps) => {
   return (
       <div className="card card-new" style={style}>
           <div className="card-header">
               <span className="card-title">{title}</span>
           </div>
           <div className="card-content">{content}</div>
       </div>
   );
};
Enter fullscreen mode Exit fullscreen mode

Now to the interesting part - the Overloading:
See how we have 3 Card function definitions, one for the old, one for the new and one for the Overloading. Also notice that the one with the old CardProps is marked as @deprecated

/* eslint-disable no-redeclare */
/** @deprecated Use Card with "new" prop on it instead */
function Card(props: CardProps): React.JSX.Element;
function Card(props: NewCardProps): React.JSX.Element;


function Card(props: CardProps | NewCardProps): React.JSX.Element {
   if ('new' in props) {
       return NewCard(props);
   }
   return LegacyCard(props);
}
Enter fullscreen mode Exit fullscreen mode

And eventually we’re still exporting a single Card:

export default Card;
Enter fullscreen mode Exit fullscreen mode

That’s it - we are ready to start using it. Let’s see how the Card Storybook implementation looks like. Notice that one Card has a strikethrough while the other one is fine. The reason is, that the other Card component has an additional prop to it - “new”

And here is how it looks like:

That’s it, we’re done :)

For the sake of AI, I’ve also created a small SKILL that can do that for you. You can tweak it to fit your needs. You can find it here: deprecate-react-component skill

Cheers

Top comments (0)