Skip to content

fix: Deep-compare constraints instead of JSON.stringify-ing them - #4167

Merged
mrousavy merged 1 commit into
mainfrom
fix/memoize-constraints
Aug 21, 2026
Merged

fix: Deep-compare constraints instead of JSON.stringify-ing them#4167
mrousavy merged 1 commit into
mainfrom
fix/memoize-constraints

Conversation

@mrousavy

Copy link
Copy Markdown
Owner

What

useCameraController kept the user's constraints stable by putting JSON.stringify(constraints) into the useMemo dependency array:

// biome-ignore lint/correctness/useExhaustiveDependencies: It's an array of objects, we either have to deep-memo or just stringify.
const stableConstraints = useMemo<Constraint[]>(() => { ... }, [JSON.stringify(constraints), stableOutputs])

That works for plain constraints like { fps: 60 }, but it silently breaks for { resolutionBias: someOutput }.

Nitro HybridObjects are created via Object.create(prototype) and hold every property on their shared prototype, so the JS object itself has no own keys. JSON.stringify(...) therefore serializes every CameraOutput to the same string:

build JSON.stringify(photoOutput)
release {}
debug {"__type":"HybridObject<CameraPhotoOutput>"}

Two different outputs of the same type are indistinguishable. So this, which is the documented way to express capture priority:

constraints={photoFirst
  ? [{ resolutionBias: photoOutput }, { resolutionBias: videoOutput }]
  : [{ resolutionBias: videoOutput }, { resolutionBias: photoOutput }]}

is invisible to the memo in release builds, and the session is never re-configured.

How

Replaces the stringification with an explicit deep comparison (useMemoizedConstraints(...)) that compares object literals and arrays by value, and everything else (i.e. HybridObjects) by identity. It also drops a full JSON serialization from every render, and lets the useMemo dependency array be honest again (the biome-ignore is gone).

Test

Adds two Harness tests in visioncamera.hooks.harness.tsx:

  1. re-renders a component with an inline constraints={[{ fps }]} array and asserts onConfigured fired exactly once, then asserts changing the actual fps value re-configures exactly once more.
  2. re-points a resolutionBias constraint at a different CameraPhotoOutput while the attached outputs stay identical, and asserts the session re-configures. Both bias outputs are deliberately the same HybridObject type, which is exactly what JSON.stringify(...) could not tell apart - this test is red on main.

Note

Touches the same import block in visioncamera.hooks.harness.tsx as #4166, so whichever lands second will need a trivial rebase.

馃 Generated with Claude Code

@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-native-vision-camera-docs Ready Ready Preview Aug 21, 2026 1:26pm

Request Review

`useCameraController` kept the user's `constraints` stable by putting
`JSON.stringify(constraints)` into the `useMemo` dependency array. That
works for plain constraints like `{ fps: 60 }`, but it silently breaks for
`{ resolutionBias: someOutput }`.

Nitro `HybridObject`s are created via `Object.create(prototype)` and hold
every property on their shared prototype, so the JS object itself has no own
keys. `JSON.stringify(...)` therefore serializes *every* `CameraOutput` to
`{}` (release) or `{"__type":"HybridObject<CameraPhotoOutput>"}` (debug) -
two different outputs of the same type produce the exact same string. Any
change that only re-points a `resolutionBias` at a different output is
invisible to the memo, and the session is never re-configured.

Replace the stringification with an explicit deep comparison that compares
object literals and arrays by value and everything else (i.e. `HybridObject`s)
by identity. That also drops a full JSON serialization from every render.

Also adds two Harness tests: one that re-renders with an inline `constraints`
array and asserts the session is configured exactly once (and re-configured
once more when the constraint values actually change), and one that asserts
re-pointing a `resolutionBias` constraint at a different output of the same
type does re-configure the session.
@mrousavy
mrousavy force-pushed the fix/memoize-constraints branch from 13d4770 to 82e22f0 Compare August 21, 2026 13:24
@mrousavy
mrousavy merged commit 8015396 into main Aug 21, 2026
2 of 3 checks passed
@mrousavy
mrousavy deleted the fix/memoize-constraints branch August 21, 2026 13:24
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.

1 participant