Advanced React Native Best Practices
Jaydeep A.
Oct 30, 2025
Building a React Native app from scratch can feel like juggling five different problems at once — network calls, rendering quirks, Git conflicts, performance drops, and that mysterious iOS Pod error that appears right before release day.
Read our previous blog on React Native coding series here.
This post is a compact guide for engineers working in bare React Native setups. It walks through clean API integration with Axios and TypeScript models, practical debugging patterns that actually save time, small UX tweaks that make a big difference, and the Git habits that keep your project maintainable. Think of it as hard-earned field notes distilled into patterns you can drop directly into your next build.
TL;DR (quick overview)
- API integration: centralize logic using Axios, typed request/response models, and service wrappers.
- API error debugging: follow a structured checklist, base URL, headers, body, query params, HTTP status handling.
- UX & media optimization: use image caching/preloading, skeleton loaders, in-button loaders, and network strategies for perceived performance.
- Version control: small, frequent commits, feature branches, merge requests, and clear commit messages.
-
Common pitfalls: TypeScript
.tsvs.tsx, dependencies classification, memoization (memo, useMemo, useCallback),useStatevsuseRef,forwardRefpatterns, scroll/list optimizations, Pod management, and reusable hooks.
API Integration
Why centralize API logic
Centralizing API configuration and service calls makes the codebase maintainable, testable, and easy to swap environments (dev/staging/prod). It also lets you implement cross-cutting concerns (auth token refresh, logging, global error handling) in one place.
Recommendations
- Prefer Axios over fetch for interceptors, base URL, request/response transforms, and timeout support.
- Always use try–catch–finally and handle errors gracefully.
- Type request/response models for every API call (TypeScript).
- Folder structure:
Axios Instance Example
Service Wrapper Example
API Error Debugging
1. Base URL
— Ensure environment-specific baseURL (dev/staging/prod) is correct.
— Confirm .env is loaded for the running build.
2. Endpoint path
— Exact path check: /user/profile vs /users/profile, typo is common.
3. Headers
— Auth tokens (Authorization), Content-Type, custom headers.
— Expired tokens causing 401s, verify on server or via JWT decode.
4. Body (POST/PUT)
— Validate keys, types, required fields, server will often respond 400 for mismatch.
5. Request method
— GET vs POST vs PUT, using wrong method returns 405/404 often.
6. Query params
— Check pagination params, filters, or missing IDs.
7. Error handling
— Handle HTTP codes: 400 (client), 401 (auth), 403 (forbidden), 404 (not found), 500 (server).
— Implement retry/backoff for idempotent calls where appropriate.
8. Response structure
— Validate data, error, and message shapes, backend contracts change.
Debugging tips
- Reproduce with Postman/curl to isolate client vs server issues.
- Add detailed client logs: method, url, headers (redact secrets), payload, and full response.
- If intermittent, check network layer (VPN, firewall), and rate limiting.
- Use source maps / dev builds while investigating client-side stack traces.
UX & Media Optimization
Images & Caching
-
Use
react-native-fast-imagefor better caching and performance overImage. - Avoid loading huge images, prefer appropriate sizes or server-side thumbnails / CDN transformations.
- Preload critical images (logos, placeholders, avatars) at app launch when it makes sense:
Loaders And Perceived Performance
- Prefer in-button or in-modal loaders instead of blocking full-screen spinners for small actions.
- For list content use skeleton placeholders for perceived speed.
- For long requests, show progress or estimated time (if available).
Network Strategies
- Cache GET responses and show cached data while revalidating in background.
- Use optimistic updates for snappy UI on create/update flows, then reconcile on server response.
Version Control (Git)
Best practices
- Frequent, small commits with clear messages (what + why).
- Use feature branches and merge requests in GitLab for peer review.
- Keep PRs small: easier to review, test, and revert.
- Avoid pushing large unreviewed changes, split across commits/PRs.
Commit message style (example)
Branch strategy suggestions
-
main / master: production -
develop: integration / staging -
feature branches:
feat/<feature-name> -
hotfix:
hotfix/<issue>
Common Mistakes : Explanations & Quick Fixes
ts vs tsx
-
.tsxrequired when file contains JSX. Use.tsfor plain TypeScript modules.
dependencies vs devDependencies -
Runtime packages (React, React Native libs) →
dependencies. -
Build/test tools (jest, typescript, metro configs) →
devDependencies. - Mistake: shipping dev tools to production increases bundle size.
Git & GitLab mistakes
-
Huge PRs, missing tests, not updating branches, keep small, rebase/update before merge.
memo vs useMemo vs useCallback -
React.memo(Component) :memoizes rendered output of component to skip re-render when props are identical. -
useMemo(() => expensiveValue, [deps]) :memoizes a computed value. -
useCallback(() => fn, [deps]): memoizes function identity (useful for stable props to child components). -
Rule: optimize only when you have an actual render/perf problem. Measure before optimizing.
useState vs useRef -
useStatetriggers re-render on change. -
useRefholds mutable data without causing re-render (good for storing DOM/node refs, scroll positions, or previous values used in event handlers). -
Use refs for pagination cursors or last known scroll offsets to avoid unnecessary re-renders..
forwardRef & useImperativeHandle -
Use
forwardRefto expose imperative methods from child to parent (e.g.,focus()on TextInput). -
Use
useImperativeHandleto control which values are exposed.
Avoid unnecessary re-renders in pagination / scrolling
-
Use refs for current page/cursor and
FlatListoptimizations (keyExtractor, getItemLayout, windowSize, initialNumToRender). - Avoid storing frequently updating scroll position in state.
iOS Pods: Don’t delete Pods/ or Podfile.lock immediately
-
Deleting them is a last resort. Troubleshoot mismatched versions, run
pod installand checkPodfile.lockdiffs first.
Custom hooks for reusable logic
- Pull repeated logic (API calls, local caching, input state) into hooks to keep components thin and testable.
Function types: normal vs arrow vs callbacks
-
Arrow functions inside renders can cause child re-renders if passed as props, memoize with
useCallbackwhere needed. - Prefer plain functions for module-level helpers.
Before using AI for debugging
- Follow a 70–30 rule: 70% investigative/manual debugging (GitHub issues, logs, local repros, StackOverflow) and 30% AI help. This avoids over-reliance and gives better prompts to AI.
Quick Patterns & Snippets
Standard try/catch/finally pattern
Small custom hook (API + cache example)
Conclusion
To wrap it up, this post pulled together some practical, real-world advice for building stable bare React Native apps. Keep your API logic clean and centralized with Axios interceptors and typed models. Use a solid debugging checklist to trace issues faster. Make the UX smoother with smart image caching, preloading, and non-blocking loaders. Keep your Git history tidy with small, clear commits and structured merge requests. Stay alert to common pitfalls — TypeScript extensions, dependency mix-ups, hook misuse, and iOS Pod quirks can sneak up on you. And finally, stick to the 70–30 rule when using AI: do your digging first, then ask focused questions to get the best answers.