React Native Roadmap 2026-W31
Week of July 27βAugust 2, 2026
Items This Week
| # | Title | Label | Link |
|---|---|---|---|
| 1 | Building a Real-Time Face Recognition App with VisionCamera | π¦ RN | Read |
| 2 | VisionCamera v5.2 β zoom, exposure & torch on SkiaCamera | π¦ RN | Read |
| 3 | Sign in with Google for React Native | π¦ RN | Read |
| 4 | Octane: React's Programming Model, Compiled | βοΈ REACT | Read |
| 5 | TanStack stopped using RSC on tanstack.com | βοΈ REACT | Read |
| 6 | Making Referential Stability a Type with StableRef | βοΈ REACT | Read |
| 7 | React Navigation 8 / Screens 5 progress | π¦ RN | Read |
| 8 | Workers / Worklets multi-threading in React Native | π¦ RN | Read |
5-Day Action Plan
Β
π¦ Chunk 1 β Upgrade VisionCamera to v5.2 with SkiaCamera Controls
Goal: Give users precise camera control (zoom, exposure, torch) rendered directly on a Skia canvas surface, with no native module rebuild required β shipped as a one-day dependency bump.
Scope:
- Bump
react-native-vision-camerato^5.2.0inpackage.json - Ensure
@shopify/react-native-skiais at a compatible version - Add
zoom,exposure, andtorchprops to the existing<SkiaCamera>usage - Wire a slider UI component to each control prop on the camera screen
- Smoke-test on iOS Simulator and Android Emulator
Out of scope: Recording or photo capture flow changes, frame processor logic, custom Skia overlays.
Dependencies: Existing VisionCamera v5.x integration; React Native 0.73+.
Acceptance criteria:
- Users can pinch-to-zoom or use a slider to adjust the zoom level live on the
<SkiaCamera>preview - An exposure slider shifts the brightness of the Skia camera surface in real time
- A torch toggle button turns the LED on/off without restarting the camera session
- No previously working camera features regress (photo, video, existing overlays)
Estimated effort: S
**Copy/paste this prompt:**
Implement the following React Native chunk for your mobile app: Upgrade VisionCamera to v5.2 and expose the new zoom, exposure, and torch controls on <SkiaCamera>.
Goal: Use the new SkiaCamera control props introduced in VisionCamera 5.2 (https://github.com/mrousavy/react-native-vision-camera/releases/tag/v5.2.0) to let users adjust zoom, exposure, and torch from the React Native layer.
Files to create or modify:
package.jsonβ bumpreact-native-vision-camerato^5.2.0screens/CameraScreen.tsxβ add slider and toggle components wired to camera propscomponents/CameraControls.tsxβ new component encapsulating the three controls
Step-by-step implementation:
- Update dependency:
npm install react-native-vision-camera@^5.2.0 - Run
npx pod-install(iOS) and sync Gradle (Android). - In
screens/CameraScreen.tsx, importzoom,exposure, andtorchfrom state:
const [zoom, setZoom] = useState(1);
const [exposure, setExposure] = useState(0);
const [torch, setTorch] = useState<'off' | 'on'>('off');
<SkiaCamera
device={device}
isActive
zoom={zoom}
exposure={exposure}
torch={torch}
/>
- Create
components/CameraControls.tsxwith:- A
Slider(orPanGestureHandler) for zoom (range 1βdevice.maxZoom) - A
Sliderfor exposure (range -1 to +1) - A
Pressabletorch toggle button
- A
- Pass
onZoomChange,onExposureChange,onTorchTogglecallbacks from the screen. - Smoke-test on both platforms.
Acceptance criteria checklist:
-
react-native-vision-cameraat 5.2.x inpackage-lock.json - Zoom slider updates the live preview without restarting the camera session
- Exposure slider visibly shifts preview brightness
- Torch toggle turns the LED on and off
- No regression on existing camera capture flow
π¦ Chunk 2 β Build On-Device Face Detection Pipeline with VisionCamera Frame Output
Goal: Add real-time face detection to a camera screen entirely on-device, so features like event check-in, attendance, or face-aware overlays work without sending frames to a server.
Scope:
- Set up
useFrameOutputhook with 640Γ480 YUV resolution anddropFramesWhileBusy: true - Integrate a YuNet face detector via a TFLite / LiteRT native binding
- Draw bounding box overlays over detected faces using
@shopify/react-native-skia - Enforce
try/finally frame.dispose()in the worklet callback to prevent pool exhaustion - Document the pipeline stages in a
FACE_DETECTION.mdfile
Out of scope: Face recognition/embedding, anti-spoofing, liveness detection, cloud APIs, full ML pipeline beyond detection.
Dependencies: VisionCamera v5+; @shopify/react-native-skia; a TFLite native binding (e.g. react-native-fast-tflite).
Acceptance criteria:
- Front camera feed renders real-time bounding boxes around detected faces at β₯ 15 FPS on a mid-range device
- Rapid camera switch does not crash the app (pool exhaustion guard verified)
- No memory leak: frames in the pool never exceed the camera's buffer count
- A product stakeholder can see the face-detection UI working on a physical device
Estimated effort: M
**Copy/paste this prompt:**
Implement the following React Native chunk for your mobile app: on-device real-time face detection using VisionCamera Frame Output and Skia overlays (reference: https://blog.margelo.com/on-device-face-recognition-react-native).
Goal: Build the first stage of a face-recognition pipeline β face detection β running entirely on device, with no server calls.
Files to create or modify:
screens/FaceDetectionScreen.tsxβ main screen with camera + overlayhooks/useFaceDetector.tsβ worklet-safe frame output hookcomponents/FaceOverlay.tsxβ Skia canvas drawing bounding boxesFACE_DETECTION.mdβ short explanation of the pipeline stages
Step-by-step implementation:
- Install dependencies:
npx expo install react-native-vision-camera @shopify/react-native-skia react-native-fast-tflite - Download the YuNet model (
face_detection_yunet_2023mar.tflite) and place it inassets/models/. - Create
hooks/useFaceDetector.ts:
import { useFrameOutput } from 'react-native-vision-camera';
export function useFaceDetector(onFaces: (boxes: Rect[]) => void) {
return useFrameOutput({
targetResolution: { width: 640, height: 480 },
pixelFormat: 'yuv',
dropFramesWhileBusy: true,
onFrame(frame) {
'worklet';
try {
// run YuNet inference here, call onFaces with bounding boxes
} finally {
frame.dispose(); // non-negotiable
}
},
});
}
- In
screens/FaceDetectionScreen.tsx, compose<Camera>with the frame output and a Skia canvas overlay. - In
components/FaceOverlay.tsx, use<Canvas>+<Rect>from@shopify/react-native-skiato draw bounding boxes. - Test on a physical device: confirm bounding boxes appear and track faces at β₯ 15 FPS.
Acceptance criteria checklist:
- Front camera renders bounding boxes around faces in real time
- FPS β₯ 15 on a Galaxy S21-class device (visible via DevTools overlay)
-
frame.dispose()is called infinallyβ no pool exhaustion after 30 seconds - App does not crash on rapid front/back camera switch
-
FACE_DETECTION.mddocuments the 5 pipeline stages
π¦ Chunk 3 β Add Google Sign-In via @thoughtbot/react-native-social-auth
Goal: Let users authenticate with their Google account on both iOS and Android using a modern, type-safe library with a first-party Expo config plugin β replacing any manual OAuth plumbing.
Scope:
- Install
@thoughtbot/react-native-social-auth - Configure the Expo config plugin in
app.config.tswith iOS and Android client IDs - Implement a "Sign in with Google" button and handle the token exchange
- Store the resulting access token with
expo-secure-store - Implement sign-out (clear token, navigate to login)
Out of scope: Apple Sign-In, additional social providers, backend token verification endpoint.
Dependencies: Expo project; expo-secure-store; Google Cloud project with OAuth credentials.
Acceptance criteria:
- Tapping "Sign in with Google" opens the native Google sign-in sheet on both platforms
- Successful sign-in stores the access token in secure storage
- Sign-out clears the token and returns the user to the login screen
- Works on both iOS Simulator (with a Google account) and a physical Android device
Estimated effort: S
**Copy/paste this prompt:**
Implement the following React Native chunk for your mobile app: Google Sign-In using @thoughtbot/react-native-social-auth (reference: https://thoughtbot.com/blog/sign-in-with-google-for-react-native).
Goal: Add a production-ready Google Sign-In flow to the app with minimal boilerplate, using the Expo config plugin to handle native configuration automatically.
Files to create or modify:
app.config.tsβ register the config plugin with OAuth client IDsscreens/LoginScreen.tsxβ add the "Sign in with Google" buttonhooks/useGoogleAuth.tsβ encapsulate sign-in, sign-out, and token storageconstants/auth.tsβ export typed auth constants
Step-by-step implementation:
- Install:
npx expo install @thoughtbot/react-native-social-auth expo-secure-store - In
app.config.ts:
plugins: [
['@thoughtbot/react-native-social-auth', {
googleIosClientId: process.env.GOOGLE_IOS_CLIENT_ID,
googleAndroidClientId: process.env.GOOGLE_ANDROID_CLIENT_ID,
}]
]
- Add the client IDs to
.env.local(never commit them). - Create
hooks/useGoogleAuth.ts:
import { signInWithGoogle } from '@thoughtbot/react-native-social-auth';
import * as SecureStore from 'expo-secure-store';
export function useGoogleAuth() {
const signIn = async () => {
const result = await signInWithGoogle();
if (result.type === 'success') {
await SecureStore.setItemAsync('accessToken', result.accessToken);
}
return result;
};
const signOut = async () => {
await SecureStore.deleteItemAsync('accessToken');
};
return { signIn, signOut };
}
- In
screens/LoginScreen.tsx, add a<Pressable>that callssignIn(). - Run
npx expo prebuildthen test on simulator and Android device.
Acceptance criteria checklist:
- Native Google sign-in sheet appears on iOS and Android
- Access token is stored in
expo-secure-storeafter successful sign-in - Sign-out removes the token and navigates back to the login screen
- No OAuth client IDs are committed to the repository
- Works on both iOS Simulator and a physical Android device
π¦ Chunk 4 β Migrate to React Navigation 8 + Screens 5
Goal: Adopt the latest React Navigation 8 API and react-native-screens v5 to benefit from improved native animations, the new usePreventRemove hook, and reduced bundle size β as highlighted in TWIR #292.
Scope:
- Bump
@react-navigation/native,@react-navigation/native-stack, andreact-native-screensto their v8/v5 releases - Resolve any breaking API changes (renamed props, removed options, updated hook signatures)
- Replace any deprecated
navigation.dangerouslyGetParent()calls with the newuseNavigationpatterns - Update the root
NavigationContainerif the new version requires config changes - Run the full E2E test suite to confirm no navigation regressions
Out of scope: Migrating to Expo Router, adding new navigation stacks, redesigning the navigation structure.
Dependencies: Existing React Navigation v7 setup; react-native-screens v4; React Native 0.73+.
Acceptance criteria:
- All screens navigate without crashes or visual regressions on iOS and Android
- Deprecated APIs replaced (TypeScript compiler reports zero navigation-related type errors)
- Back gesture and swipe animations work correctly on both platforms
- E2E tests pass after the upgrade
Estimated effort: M
**Copy/paste this prompt:**
Implement the following React Native chunk for your mobile app: migrate from React Navigation 7 to React Navigation 8 + react-native-screens v5 (reference: https://thisweekinreact.com/newsletter/292).
Goal: Upgrade the navigation stack to React Navigation 8 and Screens 5, resolving all breaking changes so the app ships on the latest stable navigation infrastructure.
Files to create or modify:
package.jsonβ bump navigation packagesApp.tsx/app/_layout.tsxβ updateNavigationContainerif needed- Any screen file using deprecated APIs (search for
dangerouslyGetParent,addListener('focus', ...)patterns) __tests__/navigation.test.tsβ update snapshots if needed
Step-by-step implementation:
- Upgrade packages:
npm install @react-navigation/native@^8.0.0 \
@react-navigation/native-stack@^8.0.0 \
react-native-screens@^5.0.0 \
react-native-safe-area-context@latest
- Run
npx pod-installand sync Gradle. - Check the React Navigation v8 migration guide for breaking changes.
- Fix TypeScript errors: run
npx tsc --noEmitand resolve each error. - Search for
dangerouslyGetParentand replace withuseNavigation+useRoutepatterns. - Run
npx jestβ fix any failing navigation snapshot tests. - Test back gesture on iOS physical device and Android emulator.
Acceptance criteria checklist:
- All navigation packages at v8/v5 in
package-lock.json - Zero TypeScript errors related to navigation types
-
npx jestpasses with no navigation test failures - Back swipe gesture works on iOS and Android after upgrade
- No visual regression on any existing screen transition
βοΈ Chunk 5 β Enforce Referential Stability with the StableRef Type Pattern
Goal: Prevent subtle performance regressions caused by unstable callbacks and objects being passed as props, by encoding memoization requirements directly in the TypeScript type system with Stable<T> β making broken memoization a compile-time error rather than a runtime mystery.
Scope:
- Install
stablereflibrary - Annotate the 5 most performance-critical component prop types with
Stable<T>(typically callbacks in list items, shared context values, and frequently re-rendered form handlers) - Fix any type errors surfaced by the new annotations (wrap values with
useCallback/useMemo) - Add ESLint
react-hooks/exhaustive-depsrule to CI to catch future violations - Document the pattern in a
PERFORMANCE.mdfile
Out of scope: Full codebase annotation, React Compiler adoption, profiling tooling setup.
Dependencies: TypeScript 5+; React 18+; existing ESLint setup.
Acceptance criteria:
- At least 5 component props annotated with
Stable<T>in production components - TypeScript build fails if an unstable value (non-memoized function/object) is passed where
Stable<T>is required - All existing unit and integration tests pass after the refactor
PERFORMANCE.mddocuments the pattern with a before/after code example
Estimated effort: S
**Copy/paste this prompt:**
Implement the following React Native chunk for your mobile app: enforce memoized props using the StableRef type pattern (reference: https://www.jovidecroock.com/blog/referential-stability-types/).
Goal: Use stableref/react to tag memoized callbacks and objects with Stable<T> in the TypeScript type system, turning unmemoized prop bugs into compile-time errors.
Files to create or modify:
package.jsonβ addstablerefdependencycomponents/VirtualizedList.tsxβ annotaterenderItem,keyExtractor,onEndReachedasStable<T>contexts/AuthContext.tsxβ annotate context value callbacks asStable<T>components/FormField.tsxβ annotateonChange,onBlurasStable<T>PERFORMANCE.mdβ document the pattern.eslintrc.jsβ ensurereact-hooks/exhaustive-depsis set toerror
Step-by-step implementation:
- Install:
npm install stableref - In
components/VirtualizedList.tsx:
import type { Stable } from 'stableref';
type Props = {
renderItem: Stable<(item: Item) => ReactElement>;
keyExtractor: Stable<(item: Item) => string>;
onEndReached: Stable<() => void>;
};
- Run
npx tsc --noEmitβ fix every location where an unstable value is passed by wrapping withuseCallbackoruseMemo. - Repeat for
AuthContext.tsxandFormField.tsx. - In
.eslintrc.js, set'react-hooks/exhaustive-deps': 'error'. - Write
PERFORMANCE.mdwith a before/after example showing howStable<T>catches the bug at compile time.
Acceptance criteria checklist:
-
stablerefinstalled and imported in at least 3 component files -
npx tsc --noEmitpasses with zero type errors - Passing a plain (non-memoized) function to a
Stable<T>prop fails the TypeScript build - All existing tests pass after wrapping values with
useCallback/useMemo -
PERFORMANCE.mdexists with a clear before/after example