Skip to content

fix: don't mutate component props (crashes under React's frozen props in dev)#795

Open
nn1900 wants to merge 1 commit into
teslamotors:masterfrom
nn1900:fix/do-not-mutate-frozen-props
Open

fix: don't mutate component props (crashes under React's frozen props in dev)#795
nn1900 wants to merge 1 commit into
teslamotors:masterfrom
nn1900:fix/do-not-mutate-frozen-props

Conversation

@nn1900
Copy link
Copy Markdown

@nn1900 nn1900 commented Jun 5, 2026

Problem

Camera mutates its own props to turn optional int/float view props into sentinels for codegen:

props.zoom = props.zoom ?? -1;
props.maxZoom = props.maxZoom ?? -1;
// ...
props.allowedBarcodeTypes = props.allowedBarcodeTypes ?? supportedCodeFormats;

In a development build this throws the moment <Camera> renders:

Render Error: Cannot add new property 'zoom'

(Component stack points at Camera.ios.tsx / Camera.android.tsx.)

Why

  • React freezes element.props in dev (Object.freeze in the JSX dev runtime — React 18 and 19; release builds do not freeze).
  • Writing to a frozen object only throws in JavaScript strict mode; in sloppy mode it silently no-ops.
  • ES modules are always strict, and bundlers increasingly emit a per-module "use strict" — e.g. Metro 0.84 injects it for every sourceType: "module" file (metro-transform-worker). React Native 0.85 ships that Metro.

So on a modern toolchain (RN 0.85 / Expo SDK 56, dev build) the mutation throws. It was masked before only where the module happened to run sloppy (older Metro, or @react-native/babel-preset’s strictMode: false) — there the frozen write is a silent no-op — and in release builds, where props aren’t frozen. That’s why it hasn’t been widely reported despite shipping since v16.

This is unrelated to React.StrictMode and to the React Compiler; it’s plain JS "use strict" + React’s dev prop-freeze.

Fix

Build a new object with the defaults and spread it into NativeCamera, instead of mutating the (frozen) props. Behavior is otherwise unchanged. Applied to both Camera.ios.tsx and Camera.android.tsx (Android keeps its processColor handling).

Repro (minimal)

// dev build, RN 0.85
const Cam = React.forwardRef((props, ref) => { props.zoom = props.zoom ?? -1; return null; });
<Cam scanBarcode />   // → throws "Cannot add new property 'zoom'"

The `Camera` component mutates its own `props` to convert optional int/float
view props to sentinels for codegen:

    props.zoom = props.zoom ?? -1;
    props.maxZoom = props.maxZoom ?? -1;
    // ...

React freezes `element.props` in development, so this throws as soon as the
module runs in JavaScript strict mode:

    Render Error: Cannot add new property 'zoom'

ES modules are always strict, and bundlers increasingly emit per-module
`"use strict"` (e.g. Metro 0.84 injects it for `sourceType: "module"`), so this
crashes Camera in any dev build that freezes props (React 18 and 19 both do).
It was masked before only where the module happened to run sloppy (older Metro /
`@react-native/babel-preset` `strictMode: false`), where the frozen write is a
silent no-op, and in release builds (React doesn't freeze there).

Fix: build a new object with the defaults and spread it into NativeCamera,
instead of mutating the (frozen) `props`. No behavior change otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants