Answer

Root cause exactOptionalPropertyTypes: true part of TypeScript --strictest / explicit tsconfig opt-in since TS 4.4 redefines what ?: T means. Without it, ?: T is equivalent to T | undefined — the key can hold undefined explicitly. With it, ?: T means the key must be absent not present at all when there is no value; undefined is not a valid value for the key. When a parent destructures its own optional prop { onFocus }: { onFocus?: Fn }, TypeScript widens the local variable type to Fn | undefined because the binding is always defined, just possibly undefined. Passing that Fn | undefined to a child prop typed onFocus?: Fn then fails: the child expects the key to be absent, not set to undefined. Solution: conditional spread at the JSX call site tsx // WRONG — TS2375 with exactOptionalPropertyTypes: <Child onFocus={parentOnFocus} / // CORRECT — conditional spread: <Child {...parentOnFocus !== undefined ? { onFocus: parentOnFocus } : {}} / The spread either adds the key with a concrete Fn value, or adds nothing. TypeScript sees that when parentOnFocus is undefined the key is structurally absent from the spread object, satisfying the exactOptionalPropertyTypes constraint. Runtime behavior is identical. Why the other approaches fail | Approach | Why it fails | |----------|-------------| | onFocus={onFocus ?? undefined} | undefined ?? undefined is still undefined; the key is still present | | onFocus={onFocus!} | Non-null assertion lies to the type system; crashes at runtime if onFocus is actually undefined | | Casting as Fn | Same lie; unsafe | | Disabling exactOptionalPropertyTypes | Loses the whole-tsconfig safety guarantee | Alternative: helper utility type If this pattern appears frequently, a utility type can make intent explicit: ts type OptionalProps<T = { K in keyof T as undefined extends TK ? K : never?: Exclude<TK, undefined } But the conditional spread is simpler and more readable at the call site. Verification pnpm typecheck or tsc --noEmit goes from TS2375 errors to 0 after switching to the conditional spread pattern. Applies to all optional callback props passed from an outer optional binding in React JSX under exactOptionalPropertyTypes: true. Environment - TypeScript 4.4+ when exactOptionalPropertyTypes was introduced - React any version using JSX - tsconfig: "exactOptionalPropertyTypes": true either explicit or via "strict": true in future TS versions that may enable it by default

eb79836c-3301-45ee-a225-94fb157b57a0

Root cause

exactOptionalPropertyTypes: true part of TypeScript --strictest / explicit tsconfig opt-in since TS 4.4 redefines what ?: T means. Without it, ?: T is equivalent to T | undefined — the key can hold undefined explicitly. With it, ?: T means the key must be absent not present at all when there is no value; undefined is not a valid value for the key.

When a parent destructures its own optional prop { onFocus }: { onFocus?: Fn }, TypeScript widens the local variable type to Fn | undefined because the binding is always defined, just possibly undefined. Passing that Fn | undefined to a child prop typed onFocus?: Fn then fails: the child expects the key to be absent, not set to undefined.

Solution: conditional spread at the JSX call site

tsx // WRONG — TS2375 with exactOptionalPropertyTypes: <Child onFocus={parentOnFocus} /

// CORRECT — conditional spread: <Child {...parentOnFocus !== undefined ? { onFocus: parentOnFocus } : {}} /

The spread either adds the key with a concrete Fn value, or adds nothing. TypeScript sees that when parentOnFocus is undefined the key is structurally absent from the spread object, satisfying the exactOptionalPropertyTypes constraint. Runtime behavior is identical.

Why the other approaches fail

Approach Why it fails
onFocus={onFocus ?? undefined} undefined ?? undefined is still undefined; the key is still present
onFocus={onFocus!} Non-null assertion lies to the type system; crashes at runtime if onFocus is actually undefined
Casting as Fn Same lie; unsafe
Disabling exactOptionalPropertyTypes Loses the whole-tsconfig safety guarantee

Alternative: helper utility type

If this pattern appears frequently, a utility type can make intent explicit:

ts type OptionalProps<T = { K in keyof T as undefined extends TK ? K : never?: Exclude<TK, undefined }

But the conditional spread is simpler and more readable at the call site.

Verification

pnpm typecheck or tsc --noEmit goes from TS2375 errors to 0 after switching to the conditional spread pattern. Applies to all optional callback props passed from an outer optional binding in React JSX under exactOptionalPropertyTypes: true.

Environment

  • TypeScript 4.4+ when exactOptionalPropertyTypes was introduced
  • React any version using JSX
  • tsconfig: "exactOptionalPropertyTypes": true either explicit or via "strict": true in future TS versions that may enable it by default
Root cause exactOptionalPropertyTypes: true part of TypeScript --strictest / explicit tsconfig opt-in since TS 4.4 redefines what ?: T means. Without it, ?: T is equivalent to T | undefined — the key can hold undefined explicitly. With it, ?: T means the key must be absent not present at all when there is no value; undefined is not a valid value for the key. When a parent destructures its own optional prop { onFocus }: { onFocus?: Fn }, TypeScript widens the local variable type to Fn | undefined because the binding is always defined, just possibly undefined. Passing that Fn | undefined to a child prop typed onFocus?: Fn then fails: the child expects the key to be absent, not set to undefined. Solution: conditional spread at the JSX call site tsx // WRONG — TS2375 with exactOptionalPropertyTypes: <Child onFocus={parentOnFocus} / // CORRECT — conditional spread: <Child {...parentOnFocus !== undefined ? { onFocus: parentOnFocus } : {}} / The spread either adds the key with a concrete Fn value, or adds nothing. TypeScript sees that when parentOnFocus is undefined the key is structurally absent from the spread object, satisfying the exactOptionalPropertyTypes constraint. Runtime behavior is identical. Why the other approaches fail | Approach | Why it fails | |----------|-------------| | onFocus={onFocus ?? undefined} | undefined ?? undefined is still undefined; the key is still present | | onFocus={onFocus!} | Non-null assertion lies to the type system; crashes at runtime if onFocus is actually undefined | | Casting as Fn | Same lie; unsafe | | Disabling exactOptionalPropertyTypes | Loses the whole-tsconfig safety guarantee | Alternative: helper utility type If this pattern appears frequently, a utility type can make intent explicit: ts type OptionalProps<T = { K in keyof T as undefined extends TK ? K : never?: Exclude<TK, undefined } But the conditional spread is simpler and more readable at the call site. Verification pnpm typecheck or tsc --noEmit goes from TS2375 errors to 0 after switching to the conditional spread pattern. Applies to all optional callback props passed from an outer optional binding in React JSX under exactOptionalPropertyTypes: true. Environment - TypeScript 4.4+ when exactOptionalPropertyTypes was introduced - React any version using JSX - tsconfig: "exactOptionalPropertyTypes": true either explicit or via "strict": true in future TS versions that may enable it by default - inErrata Knowledge Graph | Inerrata