Hero image for blog
October 15, 2024

Build a type-safe and maintainable ReactJS component with TypeScript

Quick Scenario:

Imagine working in a fast-paced environment where deadlines are tight, and collaboration between developers is crucial. You might have inherited someone else’s button component—one that doesn’t have clear type definitions or flexibility for different contexts. You struggle to understand how it works and spend more time fixing UI inconsistency and bugs than writing new features.

Why Read This Blog?

In this article, we’ll break down a common UI element, a Button, in a step-by-step manner, using TypeScript to demonstrate how type safety can save time and headaches in the long run. By the end of this blog, you’ll know how to:

We’ll be using the Button component to demonstrate these concepts. By the end, you’ll be equipped with hands-on knowledge of how to structure your React components in a type-safe way while maintaining style consistency using styled-components.

Why TypeScript for ReactJS Components?

Let’s jump into building a Button component with TypeScript and styled-components.

Step 1: TypeScript Interface Basics

We start with defining the props our Button component will accept. TypeScript allows us to describe the structure of these props using an interface.

interface ButtonProps {
    children: ReactNode;
    variant?: 'primary' | 'secondary';
    size?: 'small' | 'medium' | 'large';
    onClick?: () => void;
}

Step 2: Extending Built-in Types

We can extend existing HTML attributes (like button-related attributes) using TypeScript’s utility types.

interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
    variant?: 'primary' | 'secondary';
}

Step 3: Using Utility Types to Modify Props

What if we want the size prop to be required in our styled button? We can use TypeScript’s utility types like Omit to customize how the props behave.

interface StyledButtonProps extends Omit<ButtonProps, 'size'> {
    size: 'small' | 'medium' | 'large';
}

Step 4: Creating Styled Components

We use styled-components with TypeScript for typing our StyledButton.

const StyledButton = styled.button<StyledButtonProps>`
    font-size: ${({ size }) => sizeMap[size].fontSize};
    padding: ${({ size }) => sizeMap[size].padding};
`;

Step 5: Dynamic Props with Utility Types

Let’s define the sizeMap object, which dynamically sets properties based on the button’s size.


const sizeMap: Record<string, { fontSize: string; padding: string }> = {
    small: { fontSize: '0.75rem', padding: '0.625rem 1rem' },
    medium: { fontSize: '0.875rem', padding: '0.75rem 1.25rem' },
    large: { fontSize: '1rem', padding: '0.75rem 1.5rem' },
};

Step 6: Exporting Types and Components

Finally, we export both the ButtonProps type and the Button component to make them reusable.

export type { ButtonProps };
export default Button;

In Summary

Final Code

import { HTMLAttributes, ReactNode } from 'react';
import styled from 'styled-components';

interface ButtonProps extends HTMLAttributes {
    children: ReactNode;
    variant?: 'primary' | 'secondary';
    size?: 'small' | 'medium' | 'large';
    className?: string;
    onClick?: () => void;
}

const Button = ({
    variant = 'primary',
    size = 'medium',
    children,
    className,
    ...rest
}: ButtonProps): React.ReactNode => {
    return (
    
        {children}
    
    );
};

interface StyledButtonProps extends Omit {
    size: 'small' | 'medium' | 'large';
}

const StyledButton = styled.button`
    font-size: ${({ size }) => sizeMap[size].fontSize};
    padding: ${({ size }) => sizeMap[size].padding};
`;

const sizeMap: Record = {
    small: { fontSize: '0.75rem', padding: '0.625rem 1rem' },
    medium: { fontSize: '0.875rem', padding: '0.75rem 1.25rem' },
    large: { fontSize: '1rem', padding: '0.75rem 1.5rem' },
};

export type { ButtonProps };
export default Button;

Wrapping It Up

Finally, you’ve built a flexible, type-safe Button component that’s easy to maintain and extend. Imagine applying these principles to your entire codebase: the benefits of readability, collaboration, and scalability multiply. TypeScript gives you the confidence to build robust applications, and even as your team or codebase grows, you’ll be able to adapt your UI components without headaches.

Accessibility Considerations

To ensure our button is accessible to all users, we need to make sure it follows basic accessibility guidelines:

Fortunately, by extending HTMLAttributes<HTMLButtonElement>, we already inherit built-in keyboard and screen reader accessibility!

Why Styled-Components?

By using styled-components in this example, we benefit from:

Conclusion

By combining TypeScript with ReactJS and styled-components, we've created a Button component that is:

“TypeScript isn’t just about catching bugs—it’s about writing better, cleaner, and more reliable code. Try building your own component today, and see how much of a difference it can make.”

TypeScript is your ally when it comes to building maintainable and reliable UI components. This blog demonstrated how you can start building your own type-safe and stylish components with ease. Go ahead, try it out, and see the difference it makes in your next project!

Call for Contribution

I highly encourage all the developers (especially beginners) to practise creating type-safe components in your project. You can also contribute to a brand new React component library, playui-react

The goal is to build a highly extensible and accessible React component library! And it's just getting started! 🤩