Skip to content

fix: Memoize targetResolution in output hooks - #4166

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

fix: Memoize targetResolution in output hooks#4166
mrousavy merged 1 commit into
mainfrom
fix/memoize-target-resolution

Conversation

@mrousavy

Copy link
Copy Markdown
Owner

What

targetResolution (and previewImageTargetSize) are Size objects, and users almost always write them as inline object literals:

const photoOutput = usePhotoOutput({
  targetResolution: { width: 1920, height: 1080 },
})

An inline literal gets a fresh identity on every render, so the useMemo(...) inside usePhotoOutput / useVideoOutput / useFrameOutput / useDepthOutput misses its cache and creates a brand new CameraOutput on every render.

That isn't just wasteful, it self-perpetuates:

  1. New output -> the outputs array changes
  2. -> useCameraController's effect re-runs and re-configures the CameraSession
  3. -> setController(...) -> re-render
  4. -> back to 1.

The session ends up in an endless reconfigure loop and takes the app down within seconds.

How

Adds an internal useMemoizedSize(...) hook that memoizes a Size by its width/height values instead of by object identity, and uses it in all four output hooks. Outputs are now only re-created when the requested resolution actually changes.

This also covers <SkiaCamera targetResolution={...} />, which forwards straight into useFrameOutput(...).

Test

Adds a Harness test in visioncamera.hooks.harness.tsx that:

  • renders a component whose targetResolutions are inline object literals,
  • re-renders it three times with the same values, asserting the CameraPhotoOutput / CameraVideoOutput keep their identity and that onConfigured fired exactly once,
  • then re-renders with different resolution values and asserts the outputs are re-created and the session is re-configured exactly once more.

Without the fix the identity assertions fail on the first re-render.

馃 Generated with Claude Code

`targetResolution` (and `previewImageTargetSize`) are `Size` objects that
users almost always pass as inline object literals:

```tsx
const photoOutput = usePhotoOutput({
  targetResolution: { width: 1920, height: 1080 },
})
```

Those literals get a fresh identity on every render, so the `useMemo(...)`
inside `usePhotoOutput` / `useVideoOutput` / `useFrameOutput` /
`useDepthOutput` misses its cache and creates a brand new `CameraOutput`
every time.

That is not just wasteful, it self-perpetuates: a new output changes the
`outputs` array, which re-runs the `useCameraController` effect, which
re-configures the `CameraSession`, which calls `setController(...)`, which
renders again, which creates yet another output. The session ends up in an
endless reconfigure loop and takes the app down with it within seconds.

Memoize `Size` props by value (`width`/`height`) via a new
`useMemoizedSize(...)` internal hook so the outputs are only re-created
when the requested resolution actually changes.

Also adds a Harness test that re-renders a component with inline
`targetResolution` literals and asserts that the outputs keep their
identity and that the session is configured exactly once, while changing
the actual resolution values still re-configures it.
@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 20, 2026 9:33pm

Request Review

@mrousavy
mrousavy merged commit 2ae908a into main Aug 21, 2026
5 of 7 checks passed
@mrousavy
mrousavy deleted the fix/memoize-target-resolution branch August 21, 2026 13:23
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