skip to content

In a React Native codegenNativeComponent spec, what does CodegenTypes.WithDefault do, and what do other props default to natively?

level: middleimportance: nice to knowfreq 20%

answer

  1. the native side never sees undefined
  2. WithDefault<Type, Value> sets the native default
  3. the prop must be optional
  4. numbers need Int32, Float or Double
  5. string unions demand a default

basics

~10 s

WithDefault<Type, Value> sets the native default of an optional prop, used when JavaScript omits or removes it. Props without it get Codegen's type default: 0 for numbers, false for booleans, null for strings.

solid answer

~40 s

Codegen turns a component spec into native props structures, and native code has no `undefined`: every prop needs a concrete value when JavaScript does not pass one. `CodegenTypes.WithDefault<CodegenTypes.Float, 3>` records that value — the generated props start at 3, and on Android the generated delegate passes 3 to your setter when the prop arrives as `null`, which is what happens when JavaScript stops passing it. Without `WithDefault`, Codegen picks the type's default: `0` for `Int32`, `Float` and `Double`, `false` for `boolean`, `null` for `string`. The parser also enforces rules: a `WithDefault` prop must be optional (`?`), a top-level plain `number` is rejected in favour of `Int32`, `Float` or `Double`, and a string-union prop requires a default, so it is written as `WithDefault<'pen' | 'marker', 'pen'>`.

code

typescript · 18 lines
typescript
// specs/SignaturePadNativeComponent.ts
import type {CodegenTypes, ColorValue, HostComponent, ViewProps} from 'react-native';
import {codegenNativeComponent} from 'react-native';

export interface NativeProps extends ViewProps {
  strokeColor?: ColorValue;
  // native default 3; plain `number` would be rejected by Codegen
  strokeWidth?: CodegenTypes.WithDefault<CodegenTypes.Float, 3>;
  // a string union must carry a default
  penMode?: CodegenTypes.WithDefault<'pen' | 'marker', 'pen'>;
  disabled?: CodegenTypes.WithDefault<boolean, false>;
  // no WithDefault: Codegen's default for Int32 is 0
  maxStrokes?: CodegenTypes.Int32;
}

export default codegenNativeComponent<NativeProps>(
  'SignaturePad',
) as HostComponent<NativeProps>;

go deeper

for a junior

Remember that native views always need a value for each prop, and WithDefault is how a spec says what that value is.

for a middle

Explain the generated defaults (0, false, null), the optional-prop rule, the numeric type requirement and why string unions need WithDefault.

for a senior

Design props so unset and removed values reset the view predictably on both platforms, and write setters that treat every call as a full new value.

for a principal

Treat component defaults as part of a public API: agree them across platforms, document them, and version changes that alter baked-in native behaviour.

## Why component props need defaults at all A **Fabric native component** is described by a TypeScript spec whose `NativeProps` interface lists the props JavaScript may pass. **Codegen** turns that interface into typed native structures: a C++ props struct shared by both platforms, a Java interface and delegate for Android, and a protocol and helpers for iOS. Native code has no `undefined`. If JavaScript never sets `strokeWidth`, the native view still needs *some* number, and when a prop that was set is later removed, native code must learn what to go back to. That value is the prop's **default**, and it is decided at code-generation time. ## What WithDefault does `CodegenTypes.WithDefault<Type, Value>` is a type-level annotation — at runtime it is just an optional `Type` — that tells Codegen the default: ```typescript export interface NativeProps extends ViewProps { strokeWidth?: CodegenTypes.WithDefault<CodegenTypes.Float, 3>; penMode?: CodegenTypes.WithDefault<'pen' | 'marker', 'pen'>; disabled?: CodegenTypes.WithDefault<boolean, false>; } ``` The effect on the generated code: - The native props start with `strokeWidth` = 3, `penMode` = `pen`, `disabled` = `false`. - On Android, the generated `SignaturePadManagerDelegate` maps each prop name to your setter and substitutes the default when the incoming value is `null`, for example `value == null ? 3 : ...`. A removed prop therefore resets the view to the default instead of leaving the old value in place. - On iOS, the generated C++ props struct is initialised with the same defaults, so `updateProps:oldProps:` compares against well-defined values. ## Defaults when you do not write WithDefault | Spec type | Default Codegen uses | |---|---| | `CodegenTypes.Int32`, `Float`, `Double` | `0` | | `boolean` | `false` | | `string` | `null` | | String union (`'pen' \| 'marker'`) | none — Codegen requires one | So a signature pad declaring `strokeWidth?: CodegenTypes.Float` draws zero-width strokes until JavaScript passes a width, while one declaring `WithDefault<CodegenTypes.Float, 3>` works out of the box. ## Rules the Codegen parser enforces Codegen fails the build, with an explicit message, when a spec breaks these rules: 1. **A `WithDefault` prop must be optional.** Writing `strokeWidth: WithDefault<Float, 3>` without `?` fails with "must be optional if used with WithDefault<> annotation". 2. **Top-level numbers must say how wide they are.** A plain `number` prop fails with "must use a specific numeric type like Int32, Double, or Float", because each maps to a different native type. 3. **String unions need a default.** An enum-like prop without `WithDefault` fails with "A default enum value is required". 4. **Event props need an event type.** A plain function type fails; handlers must be `DirectEventHandler` or `BubblingEventHandler`. 5. **The file name matters.** Only files ending in `NativeComponent` are treated as component specs. ## Where defaults show up in native code - **Android:** your view manager implements the generated `SignaturePadManagerInterface`, whose setters such as `setStrokeWidth(view, value)` receive a concrete value on every call — the explicit one, or the default once the prop is removed. - **iOS:** your `RCTViewComponentView` subclass casts the incoming props to the generated `SignaturePadProps` in `updateProps:oldProps:` and compares fields; a prop JavaScript never set simply holds its default, so the first comparison already sees a real value. Either way, native code never needs an "is it set?" branch for a prop with a sensible default. ## Choosing defaults well - **Default to the value most screens want.** For a proof-of-delivery pad, a visible stroke width and `disabled: false`. - **Match the platform views' own defaults** when you wrap an existing view, so a prop left unset behaves the same on Android and iOS. - **Make "unset" meaningful only where it is needed.** A nullable colour, which falls back to the theme natively, is clearer than a sentinel number. - **Remember that defaults are baked in.** Changing a `WithDefault` value changes generated native code, so it needs a rebuild, not just a JavaScript reload. ## Mistakes interviewers listen for - Assuming an omitted prop arrives natively as `undefined` or "not set". - Assuming removing a prop in JavaScript leaves the native view unchanged. - Treating `WithDefault` as a runtime default that JavaScript applies. - Declaring numeric props as plain `number` and being surprised by the Codegen error.

  • What does the Android view manager's setter receive when JavaScript stops passing strokeWidth?
    The prop arrives at the generated delegate as `null`, and the delegate calls your `setStrokeWidth(view, ...)` with the Codegen default instead: 3 with `WithDefault<Float, 3>`, or 0 without it. Your setter should therefore treat every call as the full new value and never assume null means 'leave unchanged'.
  • Why does Codegen reject a plain number prop at the top level?
    Because it cannot choose the native type for you. `Int32`, `Float` and `Double` become different C++, Java and Objective-C types, with different ranges and precision. The spec must say which one it means, so the parser fails with a message asking for a specific numeric type.

saying these in an interview costs you the question

  • An omitted component prop reaches native code as undefined, so the view keeps its last value
  • WithDefault is applied in JavaScript before props are sent
  • A WithDefault prop can be required as long as the default is set
  • Plain number is fine for numeric props; Codegen infers Int32
  • Removing a prop in JSX never triggers a native setter call