Ojasa Mirai

Ojasa Mirai

ReactJS

Loading...

Learning Level

🟒 BeginnerπŸ”΅ Advanced
🎁 What are Props?πŸ“€ Passing Props to ComponentsπŸ” Reading Props in Components🎯 Props for Customizationβœ… Prop Types & ValidationπŸ”„ Props vs State⬇️ Passing Functions as PropsπŸš€ Building Reusable Components
Reactjs/Props/Prop Validation

βœ… Advanced Props Validation β€” Bulletproof Component Contracts

Advanced validation combines TypeScript's compile-time checking with runtime schema validation using libraries like Zod. This multi-layered approach prevents bugs before they reach production.


🎯 TypeScript Type Guards and Assertion Functions

Type guards enable runtime type narrowing, allowing TypeScript to understand which type a value actually is. Assertion functions go further, throwing errors when assertions fail.

Type guards are functions that return a boolean and narrow the type in your code. Assertion functions are stricterβ€”they throw when conditions aren't met, preventing further execution with incorrect types.

// Type guard for user object
interface User {
  id: string;
  name: string;
  email: string;
}

function isUser(value: any): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    typeof value.id === "string" &&
    typeof value.name === "string" &&
    typeof value.email === "string" &&
    value.email.includes("@")
  );
}

interface UserCardProps {
  user: unknown;
}

function UserCard({ user }: UserCardProps) {
  if (!isUser(user)) {
    return <div>Invalid user</div>;
  }

  // Now TypeScript knows user is User
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

// Assertion function: stricter version
function assertIsUser(value: any): asserts value is User {
  if (!isUser(value)) {
    throw new Error("Value is not a valid User");
  }
}

interface StrictUserCardProps {
  user: unknown;
}

function StrictUserCard({ user }: StrictUserCardProps) {
  assertIsUser(user); // Throws if not user
  // user is definitely User here
  return <div>{user.name}</div>;
}

<details>

<summary>πŸ“š More Examples</summary>

// Example 2: Array type guards and complex validation
interface Product {
  id: string;
  name: string;
  price: number;
  category: "electronics" | "books" | "clothing";
}

function isProduct(value: any): value is Product {
  if (typeof value !== "object" || value === null) return false;
  return (
    typeof value.id === "string" &&
    typeof value.name === "string" &&
    typeof value.price === "number" &&
    value.price > 0 &&
    ["electronics", "books", "clothing"].includes(value.category)
  );
}

function isProductArray(value: any): value is Product[] {
  return Array.isArray(value) && value.every(isProduct);
}

interface ProductListProps {
  products: unknown;
}

function ProductList({ products }: ProductListProps) {
  if (!isProductArray(products)) {
    return <div>Invalid products list</div>;
  }

  return (
    <div>
      {products.map((p) => (
        <div key={p.id}>{p.name}</div>
      ))}
    </div>
  );
}

</details>

πŸ’‘ Schema Validation with Zod

Zod is a TypeScript-first schema validation library. Define schemas once and extract types, ensuring runtime and compile-time validation stay synchronized.

Zod enables you to define data structures once and get both runtime validation and TypeScript types automatically. This eliminates the need to maintain types separately from validation logic.

import { z } from "zod";

// Define schema - single source of truth
const userSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(["admin", "user", "guest"]),
  preferences: z.object({
    newsletter: z.boolean(),
    notifications: z.boolean(),
  }).optional(),
});

// Extract type from schema
type User = z.infer<typeof userSchema>;

interface UserProfileProps {
  user: User;
  onUpdate: (user: User) => Promise<void>;
}

function UserProfile({ user, onUpdate }: UserProfileProps) {
  const handleSave = async (formData: unknown) => {
    try {
      // Validate before calling handler
      const validated = userSchema.parse(formData);
      await onUpdate(validated);
    } catch (error) {
      if (error instanceof z.ZodError) {
        console.error("Validation errors:", error.errors);
      }
    }
  };

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleSave(new FormData(e.currentTarget));
      }}
    >
      <input defaultValue={user.name} name="name" />
      <button>Save</button>
    </form>
  );
}

// Safe parsing - returns result instead of throwing
function UserCard({ user }: { user: unknown }) {
  const result = userSchema.safeParse(user);

  if (!result.success) {
    return (
      <div>
        <p>Validation failed:</p>
        <ul>
          {result.error.errors.map((err) => (
            <li key={err.path.join(".")}>{err.message}</li>
          ))}
        </ul>
      </div>
    );
  }

  return <div>{result.data.name}</div>;
}

<details>

<summary>πŸ“š More Examples</summary>

// Example 2: Complex nested schemas with refinements
const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  state: z.string().length(2),
  zipCode: z.string().regex(/^\d{5}(-\d{4})?$/),
});

const productSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
  price: z.number().positive(),
  inStock: z.boolean(),
  category: z.enum(["electronics", "books", "clothing"]),
  address: addressSchema,
  tags: z.array(z.string()).min(1).max(10),
  // Custom validation
  stock: z.number().refine(
    (val) => val >= 0,
    { message: "Stock cannot be negative" }
  ),
}).refine(
  (data) => {
    // Cross-field validation
    if (data.inStock && data.stock === 0) {
      return false;
    }
    return true;
  },
  {
    message: "Cannot mark item as in stock with 0 stock",
    path: ["inStock"],
  }
);

type Product = z.infer<typeof productSchema>;

// Discriminated unions in Zod
const eventSchema = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("user_created"),
    userId: z.string(),
    timestamp: z.date(),
  }),
  z.object({
    type: z.literal("user_deleted"),
    userId: z.string(),
    timestamp: z.date(),
  }),
  z.object({
    type: z.literal("post_created"),
    postId: z.string(),
    authorId: z.string(),
    timestamp: z.date(),
  }),
]);

type Event = z.infer<typeof eventSchema>;

</details>

πŸ”§ Superstruct for Complex Validation

Superstruct is another validation library with different strengths. It excels at complex nested structures and refinements.

import {
  object,
  string,
  number,
  boolean,
  optional,
  array,
  enum as enumType,
  validate,
  Struct,
} from "superstruct";

// Define struct
const User = object({
  id: string(),
  name: string(),
  email: string(),
  age: optional(number()),
  role: enumType(["admin", "user"]),
  preferences: object({
    theme: enumType(["light", "dark"]),
    language: string(),
  }),
});

interface UserProps {
  data: unknown;
}

function UserComponent({ data }: UserProps) {
  const [errors, setErrors] = useState<string[]>([]);

  useEffect(() => {
    const [error, userData] = validate(data, User);
    if (error) {
      setErrors([error.message]);
    } else {
      // userData is valid
      console.log(userData);
    }
  }, [data]);

  return (
    <div>
      {errors.length > 0 && (
        <ul>
          {errors.map((err) => (
            <li key={err}>{err}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

<details>

<summary>πŸ“š More Examples</summary>

// Example 2: Custom refinements with Superstruct
const EmailUser = object({
  id: string(),
  email: string(),
  confirmEmail: string(),
  name: string(),
}).refine((data) => {
  if (data.email !== data.confirmEmail) {
    throw new Error("Emails do not match");
  }
  return true;
});

// Usage with component
interface FormState {
  email: string;
  confirmEmail: string;
  name: string;
}

function EmailForm() {
  const handleSubmit = async (formData: FormState) => {
    const [error, validData] = validate(formData, EmailUser);
    if (error) {
      // Show error
    } else {
      // Submit validData
    }
  };

  return <form onSubmit={(e) => {}} />;
}

</details>

🎨 Props Validation at Component Boundaries

Validate props at component entry points to establish trust boundaries and catch errors early.

// Boundary validation wrapper
function withValidation<P extends Record<string, any>>(
  Component: React.ComponentType<P>,
  schema: z.ZodSchema<P>
) {
  return function ValidatedComponent(props: unknown) {
    const result = schema.safeParse(props);

    if (!result.success) {
      const errors = result.error.errors;
      return (
        <div style={{ padding: "16px", backgroundColor: "#fee" }}>
          <h3>Component Validation Failed</h3>
          <ul>
            {errors.map((err) => (
              <li key={err.path.join(".")}>
                {err.path.join(".")}: {err.message}
              </li>
            ))}
          </ul>
        </div>
      );
    }

    return <Component {...result.data} />;
  };
}

// Define component props
const buttonPropsSchema = z.object({
  children: z.string(),
  onClick: z.function(),
  variant: z.enum(["primary", "secondary"]).optional(),
  disabled: z.boolean().optional(),
});

interface ButtonProps extends z.infer<typeof buttonPropsSchema> {}

function Button({ children, onClick, variant = "primary", disabled }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn-${variant}`}
    >
      {children}
    </button>
  );
}

// Wrap with validation
const ValidatedButton = withValidation(Button, buttonPropsSchema);

// Usage - will validate all props
<ValidatedButton
  children="Click me"
  onClick={() => {}}
  variant="primary"
/>

πŸ”‘ Key Takeaways

  • βœ… Type guards enable runtime type narrowing with TypeScript
  • βœ… Assertion functions prevent execution with invalid types
  • βœ… Zod and Superstruct provide schema validation libraries
  • βœ… Define schemas once, extract both types and validation
  • βœ… Safe parsing provides better error handling than throwing
  • βœ… Validate at component boundaries to establish trust
  • βœ… Combine compile-time and runtime validation for safety
  • βœ… Custom refinements enable complex cross-field validation

Ready to practice? Challenges | Next: Props vs State Architecture


Resources

Python Docs

Ojasa Mirai

Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.

Learn Deep β€’ Build Real β€’ Verify Skills β€’ Launch Forward

Courses

PythonFastapiReactJSCloud

Β© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn