TypeScript Optional Property Typing#
Overview#
The project uses TypeScript strict mode and enables both exactOptionalPropertyTypes and noUncheckedIndexedAccess. With exactOptionalPropertyTypes enabled, TypeScript distinguishes between an optional property foo?: T (which can be omitted) and foo?: T | undefined (which can be omitted or explicitly set to undefined). This stricter mode catches type errors at compile time and ensures that optional properties are only omitted, not explicitly set to undefined, unless the type signature explicitly includes | undefined.
The strict vs. exactOptionalPropertyTypes Distinction#
Under plain strict mode, the following distinction collapses:
- Absent property β the key is not present in the object literal at all
undefinedproperty β the key is present with valueundefined
Without exactOptionalPropertyTypes, TypeScript treats both as the same type (T | undefined), so passing { foo: undefined } to a function expecting { foo?: T } succeeds. With exactOptionalPropertyTypes enabled, TypeScript distinguishes between them and rejects the explicit-undefined case unless the type signature includes | undefined.
This codebase enables exactOptionalPropertyTypes in tsconfig.json, so the compiler enforces this distinction throughout the source.
How This Appears in Practice#
CarouselProps declares several optional properties, and with exactOptionalPropertyTypes enabled, internal type definitions that receive these values must explicitly include | undefined if they accept explicit undefined:
progress?: SharedValue<number>onProgressChange?: CarouselProgressChangeHandlerscrollOffsetValue?: SharedValue<number>
These are passed from CarouselLayout directly into useOnProgressChange, which now declares them as CarouselProgressChangeHandler | undefined and SharedValue<number> | undefined respectively to allow explicit undefined from spreads or destructured objects.
Runtime Guard Pattern#
Even though the type system now distinguishes between absent and explicit undefined, the codebase guards against falsy values at runtime before using any optional prop:
if (progress) progress.value = nextProgress;if (onProgressChange) scheduleOnRN(onProgressChange, nextProgress);
This truthy-check pattern remains the conventional mitigation and works correctly whether the property was absent or explicitly set to undefined.
Implications for Contributors#
| Scenario | Behavior |
|---|---|
Passing undefined explicitly for an optional prop typed foo?: T | Type error (compiler enforces the distinction) |
Passing undefined explicitly for an optional prop typed foo?: T | undefined | Allowed (explicit undefined is part of the type) |
Passing undefined for a required prop | Type error |
| Accessing optional prop without a guard | May throw at runtime; use truthy check or nullish coalescing |
Spreading objects with undefined-valued optional keys | Requires the receiving type to include | undefined in the signature |
Key References#
tsconfig.jsonβ compiler options (note:strict: true,exactOptionalPropertyTypes: true,noUncheckedIndexedAccess: true)src/public-types.tslines 65β91 βCarouselPropswith optionalprogressandonProgressChangesrc/hooks/useOnProgressChange.tsβ canonical example of receiving and guarding optional props with explicit| undefinedunionsrc/components/CarouselLayout.tsxlines 88β96 β call site passing optional props through- TypeScript
exactOptionalPropertyTypesdocumentation - TypeScript
noUncheckedIndexedAccessdocumentation