Showing posts with label react-19. Show all posts
Showing posts with label react-19. Show all posts

Monday, 26 January 2026

Call a React Component with TypeScript and Zod: A Step-by-Step, Production-Ready Pattern

The fastest way to call a component in React is to render it via JSX and pass strictly typed props. This article shows how to call a component in React with TypeScript and Zod so you get compile-time and runtime safety, clear state management, and production-ready patterns.

The Problem

Developers often "call" (render) a component without strict typing or validation, leading to runtime bugs, unclear state, and hard-to-test UI.

Prerequisites

Node.js 20+, pnpm or npm, React 19, TypeScript 5+, Zod 3+, a modern browser. Ensure tsconfig has strict: true.

The Solution (Step-by-Step)

Step 1: Bootstrap a minimal TypeScript + React app

// package.json (excerpt) - ensures React 19 and strict TS
{
  "name": "react-call-component-ts",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "typescript": "^5.6.0",
    "vite": "^5.0.0",
    "@types/react": "^18.3.0",
    "@types/react-dom": "^18.3.0"
  }
}
// tsconfig.json - strict mode enabled for maximum safety
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM"],
    "jsx": "react-jsx",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Step 2: Create a strictly typed child component with runtime validation

// src/components/Greeting.tsx
import React, { memo } from "react";
import { z } from "zod";

// 1) Define compile-time props shape via TypeScript
export type GreetingProps = {
  name: string;                 // Required user name
  mode: "friendly" | "formal";  // Discriminated literal union for behavior
};

// 2) Define runtime schema using Zod for additional safety in production
const greetingPropsSchema = z.object({
  name: z.string().min(1, "name is required"),
  mode: z.union([z.literal("friendly"), z.literal("formal")])
});

// 3) React.memo to avoid unnecessary re-renders when props are stable
export const Greeting = memo(function Greeting(props: GreetingProps) {
  // Validate props at runtime to fail fast in dev and log issues in prod
  const result = greetingPropsSchema.safeParse(props);
  if (!result.success) {
    // Render a small fallback and log schema errors for debugging
    console.error("Greeting props invalid:", result.error.format());
    return Invalid greeting config;
  }

  // Safe, parsed props
  const { name, mode } = result.data;

  // Render based on discriminated union value
  if (mode === "friendly") {
    return 

Hi, {name}! Welcome back.

; } return

Hello, {name}. It is good to see you.

; });

Explanation: We "call" a component in React by placing it in JSX like <Greeting name="Sam" mode="friendly" />. The TypeScript type enforces correct usage at compile time; Zod enforces it at runtime.

Step 3: Manage parent state with discriminated unions and render the child

// src/App.tsx
import React, { useEffect, useState } from "react";
import { Greeting } from "./components/Greeting";

// Discriminated union for page state: guarantees exhaustive checks
type PageState =
  | { kind: "loading" }
  | { kind: "ready"; userName: string }
  | { kind: "error"; message: string };

export function App() {
  const [state, setState] = useState({ kind: "loading" });

  // Simulate fetching the current user, then set ready state
  useEffect(() => {
    const timer = setTimeout(() => {
      // In a real app, replace with a fetch call and proper error handling
      setState({ kind: "ready", userName: "Sam" });
    }, 300);
    return () => clearTimeout(timer);
  }, []);

  // Render different UI based on discriminated union state
  if (state.kind === "loading") {
    return 

Loading…

; } if (state.kind === "error") { return

Error: {state.message}

; } // Key line: this is how you "call" (render) the component with props return (

Dashboard

{/* Rendering a list of components safely */} {(["Ada", "Linus", "Grace"] as const).map((n) => ( ))}
); }

Step 4: Mount the app

// src/main.tsx
import React from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";

const container = document.getElementById("root");
if (!container) throw new Error("Root container missing");

createRoot(container).render(
  // StrictMode helps surface potential issues
  
    
  
);

Best Practices & Security

Pro-Tip: Use React.memo for presentational components to avoid unnecessary re-renders.

Pro-Tip: Use discriminated unions for UI state to guarantee exhaustive handling and safer refactors.

Pro-Tip: Validate at runtime with Zod for boundary inputs (API responses, query params, environment-driven config).

Pro-Tip: Prefer useCallback and stable prop shapes when passing callbacks to memoized children.

Pro-Tip: Keep components pure; avoid hidden side effects inside render logic.

Security note (front-end): Do not embed secrets in the client. If you integrate with Azure or any backend, call a secured API instead of accessing resources directly from the browser.

Security note (Azure backend integration): Use Managed Identity and DefaultAzureCredential in the server/API, not the frontend. Grant the server's managed identity least-privilege RBAC roles only. Example: for Azure Storage reads, assign Storage Blob Data Reader to the API identity at the specific container scope.

Security note (data flow): Validate user input and API responses at the edge (API) with Zod or similar, then keep the front-end strictly typed.

Summary

• You call a component in React by rendering it in JSX with strictly typed, validated props.

• Discriminated unions make UI state predictable, and React.memo boosts performance.

• For real backends, keep secrets server-side, use Managed Identity with least-privilege RBAC, and validate at the edge.

Testing Quickstart

Test a component render with React Testing Library

// src/components/Greeting.test.tsx
import React from "react";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { Greeting } from "./Greeting";

test("renders friendly greeting", () => {
  render(<Greeting name="Sam" mode="friendly" />);
  expect(screen.getByText(/Hi, Sam!/)).toBeInTheDocument();
});

test("renders formal greeting", () => {
  render(<Greeting name="Ada" mode="formal" />);
  expect(screen.getByText(/Hello, Ada\./)).toBeInTheDocument();
});

This test verifies the component is "called" with valid props and renders deterministic output. For invalid props, assert that the fallback appears and console error is triggered.