weshipit.today — React Native Newsletter — 2026-W16
Week of April 13–19, 2026
Items This Week
| # | Title | Label | Link |
|---|---|---|---|
| 1 | VisionCamera 5.0 — Full Rewrite to Nitro, Constraints API, In-Memory Photos | 🟦 RN | Read |
| 2 | Pulsar — Universal Haptic Feedback Library (Software Mansion) | 🟦 RN | Read |
| 3 | Nitro Fetch 1.0 — HTTP/3, drop-in fetch replacement for React Native | 🟦 RN | Read |
| 4 | How to keep your OTA updates lean and fast | 🟧 EXPO | Read |
| 5 | Expo Series B — $45M raised, Expo Agent announced | 🟧 EXPO | Read |
| 6 | TanStack Start — React Server Components (experimental) | ⚛️ REACT | Read |
| 7 | The Vertical Codebase — Domain-first folder structure for React | ⚛️ REACT | Read |
| 8 | React Native 0.85.1 — New Shared Animation Backend (experimental) | 🟦 RN | Read |
5-Day Action Plan
🟦 Chunk 1 — Upgrade VisionCamera to v5 (Nitro + Constraints API)
Goal: Replace VisionCamera V4 with V5 to gain ~15× faster native calls via Nitro Modules, eliminate camera session crashes on Android, and adopt the new Constraints API as a single source of truth for camera configuration.
Scope:
- Uninstall
react-native-vision-camera@4, installreact-native-vision-camera@5,react-native-nitro-modules, andreact-native-nitro-image - Migrate
<Camera />props: replacevideo={true}/photo={true}withoutputs={[photoOutput, videoOutput]}usingusePhotoOutput()/useVideoOutput()hooks - Replace
useCameraFormat()+format={}with the newconstraints={[...]}prop - Replace
camera.ref.takePhoto()withphotoOutput.capturePhoto({})returning an in-memoryPhotoobject - Display captured photos via
<NitroImage image={await photo.toImageAsync()} />instead of reading a temp file
Out of scope: Frame processor migration, barcode scanner plugin, depth streaming, RAW capture, multi-cam sessions.
Dependencies: React Native New Architecture enabled (required by Nitro Modules).
Acceptance criteria:
- Camera opens without crashes on a physical iOS device
- Camera opens without crashes on a physical Android device
- Photo capture returns an in-memory
Photoand is displayed immediately (no temp file read) - No
useCameraFormatcalls remain in the codebase npx tsc --noEmitpasses with zero errors
Estimated effort: M
<details> <summary><strong>Copy/paste this prompt:</strong></summary>Implement the following React Native chunk for your mobile app : Upgrade VisionCamera from v4 to v5 (Nitro-based) to gain ~15x faster native calls, fix camera session crashes on Android, and use the new Constraints API. Goal: Replace all VisionCamera v4 usage with v5's new output-based and constraint-based API. Files to create or modify: package.json — update react-native-vision-camera to @5, add react-native-nitro-modules and react-native-nitro-image. Any screen or component importing react-native-vision-camera — update API usage. CameraScreen.tsx (or equivalent) — migrate to new outputs + constraints API. Step-by-step instructions: 1. Run npm uninstall react-native-vision-camera && npm install react-native-vision-camera@5 react-native-nitro-modules react-native-nitro-image. 2. Run npx pod-install and rebuild native. 3. Replace photo capture on cameraRef with const photoOutput = usePhotoOutput() and await photoOutput.capturePhoto({}). 4. Replace format={format} + useCameraFormat() with constraints={[{ fps: 30 }]} directly on <Camera />. 5. Replace video={true} / photo={true} boolean props with outputs={[photoOutput, videoOutput]}. 6. Display captured photo using const image = await photo.toImageAsync() then <NitroImage image={image} />. Acceptance criteria checklist: [ ] Camera opens on iOS without crash. [ ] Camera opens on Android without crash. [ ] Photo capture works and displays immediately (no temp file read). [ ] No useCameraFormat usage remains. [ ] npx tsc --noEmit passes with zero errors.
🟧 Chunk 2 — Optimize EAS OTA Updates: Asset Selection & Hermes Bytecode Diffing
Goal: Reduce OTA update download sizes and delivery times for production users, saving bandwidth costs and improving the update experience on cellular connections.
Scope:
- Audit bundle with Expo Atlas (
EXPO_ATLAS=true npx expo start) to identify the top heaviest assets/dependencies - Configure
updates.assetPatternsinapp.json/app.config.tsto limit which assets are shipped in updates - Run
npx expo-updates assets:verifyto confirm no required assets are excluded - Enable Hermes bytecode diffing (SDK 55 beta) in the EAS Update config
- Publish a test update and document before/after update size in the PR description
Out of scope: Migrating to a new SDK version, changing CDN provider, native code changes, CI pipeline rewrites.
Dependencies: Expo SDK 55, existing EAS Update setup (free tier is sufficient).
Acceptance criteria:
- Expo Atlas dashboard is accessible and shows bundle composition
app.jsoncontains anupdates.assetPatternsfield with relevant file patternsnpx expo-updates assets:verifypasses with no warnings- Published test update is measurably smaller than the previous baseline (logged in PR)
- App does not crash after applying the update on a physical device
Estimated effort: S
<details> <summary><strong>Copy/paste this prompt:</strong></summary>Implement the following React Native chunk for your mobile app : Shrink EAS OTA update sizes by configuring asset selection and enabling Hermes bytecode diffing (Expo SDK 55 feature), shipping faster updates to production users. Goal: Reduce OTA update download size by auditing the bundle with Expo Atlas and configuring asset selection + bytecode diffing. Files to create or modify: app.json or app.config.ts — add updates.assetPatterns. eas.json — enable experimental Hermes bytecode optimization under the production profile. Step-by-step instructions: 1. Run EXPO_ATLAS=true npx expo start, press shift+m and open Atlas. Identify the top 5 heaviest imports. 2. In app.json add: "updates": { "assetPatterns": ["**/*.png", "**/*.jpg", "assets/**/*"] }. 3. Run npx expo-updates assets:verify and fix any missing assets. 4. In eas.json under the production build profile, add "experimental": { "hermesBytecodeOptimization": true }. 5. Run eas update --channel production and compare the update size in the EAS dashboard vs. the previous update. Acceptance criteria checklist: [ ] Atlas runs and shows bundle composition breakdown. [ ] assetPatterns is configured in app config. [ ] expo-updates assets:verify passes with no warnings. [ ] Published update size is smaller than the previous baseline (note delta in PR). [ ] No app crashes on physical device after the update.
🟦 Chunk 3 — Implement Pulsar Haptic Feedback on Key Interactions
Goal: Add tactile feedback to primary interactive elements (buttons, destructive actions, swipe gestures) using Pulsar (Software Mansion) to improve perceived responsiveness and overall UX polish.
Scope:
- Install
@swmansion/pulsar - Create a
useHapticswrapper hook exposinglight,medium,success, anderrorpatterns - Apply
lighthaptics on primary CTA buttons - Apply
error/warninghaptics on destructive actions (delete, logout, discard) - Apply
successhaptics on form submission confirmation
Out of scope: Custom haptic sequence design (Pulsar Studio is not yet available), audio simulator previews (nice-to-have), Android-specific custom vibration patterns.
Dependencies: React Native 0.73+, physical device for acceptance testing (simulators degrade gracefully).
Acceptance criteria:
- Haptic fires on primary button press on a physical iOS device
- Haptic fires on primary button press on a physical Android device
- Destructive actions trigger
errorpattern - Form success triggers
successnotification pattern - App launches normally in simulator with no crash (graceful fallback)
Estimated effort: XS
<details> <summary><strong>Copy/paste this prompt:</strong></summary>Implement the following React Native chunk for your mobile app : Add haptic feedback using the Pulsar library from Software Mansion to key interactive elements for a polished, tactile UX. Goal: Create a reusable haptics hook and apply it to primary buttons, destructive actions, and success confirmations. Files to create or modify: package.json — add @swmansion/pulsar. hooks/useHaptics.ts — (create) wrapper hook for Pulsar patterns. components/Button.tsx (or equivalent) — call haptics.light() on press. Any destructive action handler — call haptics.error(). Any success/confirmation handler — call haptics.success(). Step-by-step instructions: 1. Run npm install @swmansion/pulsar && npx pod-install. 2. Create hooks/useHaptics.ts: import Haptics from @swmansion/pulsar and export useHaptics returning light, medium, success, error functions. 3. In components/Button.tsx, call haptics.light() in onPress. 4. In delete/logout handlers, call haptics.error() before the action. 5. In form submit success callbacks, call haptics.success(). 6. Test on a physical iOS and Android device. Acceptance criteria checklist: [ ] @swmansion/pulsar installed, no native build errors. [ ] useHaptics hook exported from hooks/useHaptics.ts. [ ] Haptic fires on primary button press on physical iOS device. [ ] Haptic fires on primary button press on physical Android device. [ ] Destructive actions use error pattern. [ ] App launches in simulator without crash.
🟦 Chunk 4 — Replace fetch() with Nitro Fetch 1.0 for HTTP/3 Performance
Goal: Improve API call latency and unlock HTTP/3 support by replacing the standard fetch() with react-native-nitro-fetch — a drop-in replacement with demonstrated performance gains on real-world apps (e.g. Bluesky).
Scope:
- Install
react-native-nitro-fetch@1.0.0(andreact-native-nitro-modulesif not already present) - Replace all
fetch()calls in the API service layer withnitroFetch() - Centralise the HTTP client in a single
lib/http.tsfile usingnitroFetch - Add optional timing logs in development mode for before/after comparison
- Verify request/response shapes are unchanged (drop-in compatibility)
Out of scope: WebSocket migration, FormData streaming, service worker setup, prefetching strategies, authentication token rotation.
Dependencies: react-native-nitro-modules installed (shared with VisionCamera V5 chunk if done), New Architecture recommended.
Acceptance criteria:
- All
fetch()calls inservices/andlib/usenitroFetch - API responses parse identically to before (same JSON shape, same error handling)
- Network calls succeed on both physical iOS and Android devices
npx tsc --noEmitpasses with zero errors- Dev-mode console shows timing logs confirming calls complete without regression
Estimated effort: S
<details> <summary><strong>Copy/paste this prompt:</strong></summary>Implement the following React Native chunk for your mobile app : Replace the global fetch() API with react-native-nitro-fetch for lower latency, HTTP/3 support, and better performance on high-frequency API calls, as demonstrated on the Bluesky app. Goal: Swap all fetch() calls in the API layer with nitroFetch from Nitro Fetch 1.0. Files to create or modify: package.json — add react-native-nitro-fetch@1 and react-native-nitro-modules. lib/http.ts — (create or modify) centralised HTTP client using nitroFetch. All files in services/ that call fetch() — update to use the centralised client. Step-by-step instructions: 1. Run npm install react-native-nitro-fetch react-native-nitro-modules && npx pod-install. 2. In lib/http.ts, import fetch as nitroFetch from react-native-nitro-fetch and export a typed httpGet<T> wrapper that adds dev-mode timing logs. 3. Update all services/*.ts files to import from lib/http.ts instead of calling fetch() directly. 4. Run the app on a physical device, open the network panel, verify all API calls succeed. 5. Compare console timing logs vs. previous baseline. Acceptance criteria checklist: [ ] react-native-nitro-fetch installed, no build errors. [ ] All fetch() in services/ replaced via centralised lib/http.ts. [ ] API calls succeed on physical iOS device. [ ] API calls succeed on physical Android device. [ ] npx tsc --noEmit passes. [ ] Dev console shows timing logs.
⚛️ Chunk 5 — Refactor to Vertical (Domain-First) Codebase Structure
Goal: Improve developer ergonomics and long-term maintainability by migrating from a horizontal file structure (components/, hooks/, utils/) to a vertical, domain-first structure for 2–3 core feature areas, following Dominik Dorfmeister's "Vertical Codebase" approach.
Scope:
- Identify 2–3 core feature domains (e.g.,
auth,profile,feed) - Create
features/[domain]/folders with collocatedcomponents/,hooks/,api/,types/subfolders - Move existing files for those domains into the new structure and update all imports
- Keep shared/cross-domain utilities under
shared/(design system, lib, global hooks) - Add TypeScript path aliases (
@features/auth,@shared) intsconfig.json - Document the new structure in
ARCHITECTURE.md
Out of scope: Migrating all features at once, updating tests (follow per-domain in future sprints), routing architecture changes.
Dependencies: None — pure refactor, no new dependencies.
Acceptance criteria:
- At least 2 feature domains exist as
features/[domain]/folders with collocated files shared/contains cross-cutting utilities and design system componentsnpx tsc --noEmitpasses with zero errors after import updates- App runs identically to before on simulator (no regressions)
ARCHITECTURE.mdexists at repo root and explains the new folder convention
Estimated effort: M
<details> <summary><strong>Copy/paste this prompt:</strong></summary>Implement the following React Native chunk for your mobile app : Migrate from a flat horizontal codebase structure (components/, hooks/, utils/) to a vertical domain-first structure for 2–3 feature areas, improving code colocation and long-term maintainability. Goal: Introduce features/[domain]/ folders with collocated components, hooks, api, and types, plus a shared layer for cross-cutting concerns. Files to create or modify: features/auth/components/, features/auth/hooks/, features/auth/api/, features/auth/types/ — move auth-related files here. features/profile/components/, etc. — same for profile domain. shared/components/, shared/hooks/, shared/lib/ — move shared utilities here. tsconfig.json — add path aliases. ARCHITECTURE.md — (create) document the new structure. Step-by-step instructions: 1. Create folder tree: src/features/auth/, src/features/profile/, src/shared/. 2. Move domain files: e.g., components/LoginForm.tsx → features/auth/components/LoginForm.tsx. 3. Add path aliases in tsconfig.json: { "paths": { "@features/*": ["src/features/*"], "@shared/*": ["src/shared/*"] } }. 4. Update all imports in moved files and their consumers. 5. Run npx tsc --noEmit and fix any broken imports. 6. Create ARCHITECTURE.md documenting the rule: "files that change together live together." Acceptance criteria checklist: [ ] features/auth/ and features/profile/ exist with collocated subfolders. [ ] shared/ contains cross-domain utilities and design system components. [ ] TypeScript path aliases configured in tsconfig.json. [ ] npx tsc --noEmit passes with zero errors. [ ] App runs on simulator without any crash. [ ] ARCHITECTURE.md exists and explains the structure.