Skip to main content

🧱 Part 3 — Building Reusable Components (Button, Input, Modal) 🎨

Reusable components are the building blocks of every React app. Instead of rewriting the same HTML over and over, we’ll build beautiful, typed, reusable components. By the end, you’ll:

  • Understand how to create typed props for UI elements.
  • Use generics for flexible components.
  • Apply the compound component pattern for advanced flexibility.

🎯 1. Why Reusable Components?

Imagine you need 20 buttons across your app. Instead of copy-pasting <button> with styles, you:

  • Create a single Button component.
  • Style it once.
  • Add props for variations (primary, secondary, etc.).
  • Reuse it everywhere with type safety.

🧩 2. Reusable Button Component

// src/components/ui/Button.tsx
import { ButtonHTMLAttributes } from 'react';
import clsx from 'clsx';

// Define the variants our button can take
type ButtonVariant = 'primary' | 'secondary' | 'danger';

type ButtonProps = {
  variant?: ButtonVariant; // style variant
  isLoading?: boolean;     // show spinner
} & ButtonHTMLAttributes<HTMLButtonElement>; // inherit all native button props

export function Button({
  variant = 'primary',
  isLoading = false,
  className,
  children,
  ...props
}: ButtonProps) {
  const base = 'px-4 py-2 rounded font-medium';
  const styles = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
    danger: 'bg-red-600 text-white hover:bg-red-700',
  };

  return (
    <button
      className={clsx(base, styles[variant], className)}
      disabled={isLoading || props.disabled}
      {...props}
    >
      {isLoading ? '⏳ Loading…' : children}
    </button>
  );
}

👉 Key points:

  • ButtonHTMLAttributes<HTMLButtonElement> → inherits all normal <button> props (onClick, disabled, etc.).
  • clsx → helps combine class names.
  • variant → only accepts 'primary' | 'secondary' | 'danger'.

Usage:

<Button variant="primary">Save</Button>
<Button variant="danger" isLoading>Delete</Button>

🧩 3. Reusable Input Component

// src/components/ui/Input.tsx
import { InputHTMLAttributes } from 'react';
import clsx from 'clsx';

type InputProps = {
  label?: string; // optional label
  error?: string; // show error message
} & InputHTMLAttributes<HTMLInputElement>;

export function Input({ label, error, className, ...props }: InputProps) {
  return (
    <div className="flex flex-col gap-1">
      {label && <label className="font-medium">{label}</label>}
      <input
        className={clsx(
          'border rounded px-3 py-2',
          error ? 'border-red-500' : 'border-gray-300',
          className,
        )}
        {...props}
      />
      {error && <span className="text-red-500 text-sm">{error}</span>}
    </div>
  );
}

Usage:

<Input label="Username" placeholder="Enter your name" />
<Input label="Email" type="email" error="Invalid email" />

👉 Key points:

  • Inherits all <input> props.
  • Adds label and error props for accessibility & validation.

🧩 4. Modal Component (with Compound Pattern)

A modal often has multiple subcomponents: header, body, footer. Instead of passing a huge list of props, we can use the compound component pattern.

// src/components/ui/Modal.tsx
import { ReactNode } from 'react';

type ModalProps = {
  isOpen: boolean;
  onClose: () => void;
  children: ReactNode;
};

export function Modal({ isOpen, onClose, children }: ModalProps) {
  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center">
      <div className="bg-white rounded-lg p-6 w-96 relative">
        <button className="absolute top-2 right-2" onClick={onClose}>❌</button>
        {children}
      </div>
    </div>
  );
}

// Subcomponents
type ModalSectionProps = { children: ReactNode };
export const ModalHeader = ({ children }: ModalSectionProps) => <h2 className="text-lg font-bold mb-2">{children}</h2>;
export const ModalBody = ({ children }: ModalSectionProps) => <div className="mb-4">{children}</div>;
export const ModalFooter = ({ children }: ModalSectionProps) => <div className="flex justify-end gap-2">{children}</div>;

Usage:

<Modal isOpen={true} onClose={() => console.log('close')}>
  <ModalHeader>Confirm Delete</ModalHeader>
  <ModalBody>Are you sure you want to delete this item?</ModalBody>
  <ModalFooter>
    <Button variant="secondary">Cancel</Button>
    <Button variant="danger">Delete</Button>
  </ModalFooter>
</Modal>

👉 Benefits:

  • Clean separation of header/body/footer.
  • Easier to compose UI.

🎨 5. Visual Appeal (Blog Styling Tip)

When blogging, show before & after screenshots:

  • Before → raw <button> HTML
  • After → styled <Button> component

Add code playgrounds (like CodeSandbox/StackBlitz) for readers to play.


✅ Wrap-Up

In Part 3, you learned:

  • How to build typed reusable components.
  • How to extend native props with HTMLAttributes.
  • How to add variants with union types.
  • How to use the compound component pattern (Modal).

👉 Next: We’ll learn state management — starting with useState, useReducer, and building typed custom hooks to manage app logic.


Comments

Popular posts from this blog

🌟 Dot net Microservices interview questions

Here is a comprehensive list of 200 .NET microservices coding questions covering all core microservices concepts and cross-cutting concerns relevant for designing, building, deploying, and maintaining .NET-based distributed systems. 🧩 A. Microservices Fundamentals (20) Build a microservice in .NET 8 that exposes a simple CRUD API. Implement communication between two microservices using REST. How would you design microservices for an e-commerce application? Create a microservice that handles user registration and login. How do you isolate domain logic in a microservice? How to apply the "Single Responsibility Principle" in microservices? Design a service registry/discovery mechanism using custom middleware. Implement a service that handles file uploads and metadata separately. Build a stateless microservice and explain its benefits. Implement health check endpoints in .NET 8. Demonstrate versioning in a microservice API. Add Swagger/OpenAPI support to your m...

⚡ Part 1: Introduction to Generics in C#

🌍 Why Do We Need Generics? Imagine you want to create a stack (like a pile of books 📚): You can push items on top You can pop items off the top If we write a stack for integers : public class IntStack { private int[] items = new int[10]; private int index = 0; public void Push(int item) => items[index++] = item; public int Pop() => items[--index]; } 👉 Problem: This only works for int . What if we want a string stack ? Or a Customer stack ? We’d have to duplicate code for every type. 😢 ✅ Solution: Generics Generics let us create type-safe reusable code without duplication. We can say: “I don’t care what type it is yet — I’ll decide later.” 1) Generic Classes Here’s a generic stack : // Generic class "Stack<T>" // The <T> is a placeholder for any type public class Stack<T> { private T[] items = new T[10]; // Array of type T private int index = 0; // Push adds an item of type T public void P...

🚪 Part 9: API Gateway for .NET 8 Microservices (Ocelot & YARP)

Once you have multiple microservices (Products, Orders, Payments…), exposing each one directly to clients gets messy: Different base URLs Duplicated auth logic No unified rate limiting / caching Hard to evolve routes or aggregate data 👉 Enter the API Gateway — your single front door for all microservices. An API Gateway handles: ✅ Routing & path rewriting ✅ Load balancing, retries, circuit breakers ✅ Authentication & Authorization (JWT, OAuth2) ✅ Rate limiting & caching ✅ Aggregation (compose results from multiple services) In this post we’ll implement two strong options: Ocelot → config-driven, mature, DevOps-friendly YARP (Yet Another Reverse Proxy) → Microsoft’s code-first, extensible gateway ⚖️ Ocelot vs YARP — When to Choose Ocelot → JSON config, minimal C#, built-in QoS (rate limit, circuit breaker). Perfect for teams that like DevOps config-as-code. YARP → full C# control, middleware-friendly, can embed into broader apps (e.g. add dashb...