
ReactJS
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.
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>
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 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>
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"
/>Ready to practice? Challenges | Next: Props vs State Architecture
Resources
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