React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

typescriptreactstrictstrictNullChecksnoUncheckedIndexedAccesssafety

Strict Mode Patterns

Recipe

Write React code that thrives under TypeScript's strictest settings. Master patterns for strictNullChecks, noUncheckedIndexedAccess, and exactOptionalPropertyTypes to catch bugs that looser settings miss.

Working Example

// strictNullChecks: handling nullable values safely
type User = {
  id: string;
  name: string;
  address?: {
    street: string;
    city: string;
  };
};
 
function UserAddress({ user }: { user: User }) {
  // Without strictNullChecks, this silently returns undefined
  // With strictNullChecks, TypeScript forces you to handle the case
  if (!user.address) {
    return <p>No address on file</p>;
  }
 
  return (
    <p>
      {user.address.street}, {user.address.city}
    </p>
  );
}
// noUncheckedIndexedAccess: safe array and object access
const colors = ["red", "green", "blue"];
 
// Without noUncheckedIndexedAccess: colors[5] is string
// With noUncheckedIndexedAccess: colors[5] is string | undefined
 
function getColor(index: number): string {
  const color = colors[index];
  if (!color) {
    return "default";
  }
  return color; // TypeScript now knows this is string, not string | undefined
}
// Safe record access
type FeatureFlags = Record<string, boolean>;
 
function isFeatureEnabled(flags: FeatureFlags, key: string): boolean {
  const value = flags[key];
  // With noUncheckedIndexedAccess, value is boolean | undefined
  return value ?? false;
}

Deep Dive

How It Works

  • strict: true enables a family of strict checks. The most impactful ones for React are strictNullChecks, strictFunctionTypes, and noImplicitAny.
  • strictNullChecks makes null and undefined distinct types that must be handled explicitly. A variable typed as string cannot be null unless you type it as string | null.
  • noUncheckedIndexedAccess adds | undefined to any index access on arrays and records. This catches the common bug of accessing an array index that might be out of bounds.
  • exactOptionalPropertyTypes distinguishes between a property that is missing and one that is explicitly undefined. { name?: string } means "name can be absent" but not { name: undefined }.
  • strictFunctionTypes enforces contravariant parameter checking on function types. This catches subtle bugs with event handler types and callback props.

Variations

Safe map/object lookups:

const userMap = new Map<string, User>();
 
function getUser(id: string): User {
  const user = userMap.get(id); // User | undefined
  if (!user) {
    throw new Error(`User ${id} not found`);
  }
  return user; // Narrowed to User
}

Non-null assertion as escape hatch:

// Use sparingly and only when you are certain the value exists
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!; // Non-null assertion
// Better: guard and throw
const ctx2 = canvas.getContext("2d");
if (!ctx2) throw new Error("Canvas 2D context not available");

Handling optional chaining with strict nulls:

type Config = {
  api?: {
    baseUrl?: string;
    timeout?: number;
  };
};
 
function getApiUrl(config: Config): string {
  return config.api?.baseUrl ?? "https://api.example.com";
}

exactOptionalPropertyTypes in action:

// With exactOptionalPropertyTypes enabled:
type Settings = {
  theme?: "light" | "dark";
};
 
// Allowed
const a: Settings = {};
const b: Settings = { theme: "light" };
 
// Error: undefined is not assignable to "light" | "dark"
// const c: Settings = { theme: undefined };

TypeScript Notes

  • Enable these incrementally if migrating: first strict: true, then noUncheckedIndexedAccess, then exactOptionalPropertyTypes.
  • The ?? (nullish coalescing) and ?. (optional chaining) operators are your best tools under strict settings. They handle null | undefined cleanly.
  • Array.prototype.find() returns T | undefined even without noUncheckedIndexedAccess. The setting mainly affects bracket access arr[i].

Gotchas

  • noUncheckedIndexedAccess adds | undefined to tuple access too. For tuples with known length, this can feel overly cautious. Use destructuring instead: const [first, second] = tuple.
  • exactOptionalPropertyTypes breaks code that explicitly passes undefined to clear optional fields. You may need to use delete obj.field instead.
  • Non-null assertions (!) bypass strict checks entirely. Every ! is a potential runtime crash. Prefer guards and narrowing.
  • Third-party library types may not be compatible with your strict settings. skipLibCheck: true helps, but it hides issues in your own .d.ts files.

Alternatives

ApproachProsCons
strict: true + noUncheckedIndexedAccessMaximum safety, catches most bugsMore verbose null handling
strict: true onlyGood balance of safety and convenienceMisses array out-of-bounds bugs
strict: false with incremental flagsGradual migration pathEasy to miss important checks
Runtime validation (Zod)Catches issues strict mode cannotAdded dependency, runtime overhead
as assertions to bypass strictnessQuick fix for type errorsDefeats the purpose of strict mode

FAQs

What checks does strict: true enable in TypeScript?
  • strictNullChecks, strictFunctionTypes, noImplicitAny, strictBindCallApply, strictPropertyInitialization, and others.
  • It is a family of flags, not a single check.
  • The most impactful ones for React are strictNullChecks, strictFunctionTypes, and noImplicitAny.
How does strictNullChecks change the way you write React components?
  • A variable typed as string can no longer be null or undefined unless explicitly typed as string | null.
  • You must handle the nullable case with guards, optional chaining, or nullish coalescing.
  • Without it, null/undefined access bugs are silent.
What does noUncheckedIndexedAccess do?
  • Adds | undefined to any index access on arrays and records.
  • colors[5] becomes string | undefined instead of string.
  • Catches the common bug of accessing an out-of-bounds array index.
What is the difference between a missing property and one set to undefined under exactOptionalPropertyTypes?
type Settings = { theme?: "light" | "dark" };
 
const a: Settings = {};            // OK: property missing
const b: Settings = { theme: "light" }; // OK
// const c: Settings = { theme: undefined }; // ERROR
  • A missing property and undefined are treated as distinct.
  • You cannot explicitly assign undefined to an optional field.
Why is the non-null assertion operator (!) risky under strict mode?
  • It bypasses strict null checks entirely, telling TypeScript "trust me, this is not null."
  • Every ! is a potential runtime crash if the value actually is null or undefined.
  • Prefer guards, optional chaining (?.), or nullish coalescing (??) instead.
Gotcha: Does noUncheckedIndexedAccess affect tuples?
  • Yes. Even tuples with known length get | undefined on bracket access.
  • This can feel overly cautious for tuples.
  • Use destructuring instead: const [first, second] = tuple avoids the | undefined.
How should you handle safe array and record lookups under strict settings?
const colors = ["red", "green", "blue"];
 
function getColor(index: number): string {
  const color = colors[index]; // string | undefined
  if (!color) return "default";
  return color; // narrowed to string
}
  • Always check for undefined after index access.
  • Use ?? for providing fallback values.
What is the recommended order for enabling strict flags incrementally?
  • First: strict: true (enables the core family of checks).
  • Second: noUncheckedIndexedAccess (adds index safety).
  • Third: exactOptionalPropertyTypes (strictest optional property handling).
Gotcha: Can third-party library types break under your strict settings?
  • Yes. Libraries may not be written with strictNullChecks or exactOptionalPropertyTypes in mind.
  • skipLibCheck: true helps by skipping type-checking of .d.ts files.
  • But this also hides errors in your own .d.ts files.
What is the difference between ?. (optional chaining) and ?? (nullish coalescing)?
  • ?. short-circuits to undefined if the left side is null or undefined: config.api?.baseUrl.
  • ?? provides a fallback only for null or undefined: value ?? "default".
  • Unlike ||, ?? does not trigger on falsy values like 0 or "".