Advanced React Native Best Practices

Advanced React Native Best Practices

Written by
Written by

Jaydeep A.

Post Date
Post Date

Oct 30, 2025

shares

1_qkuwc1TYSi6Ra27FSu6K2A

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

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

src/ api/ axios.ts # axios instance + interceptors endpoints.ts # endpoint constants services/ auth.service.ts user.service.ts models/ user.ts # request/response types

Axios Instance Example

// src/api/axios.ts import axios from "axios"; import { getAuthToken, refreshToken } from "../auth"; const api = axios.create({ baseURL: process.env.API_BASE_URL, timeout: 15000, headers: { "Content-Type": "application/json" }, }); api.interceptors.request.use(async (config) => { const token = await getAuthToken(); if (token) config.headers!.Authorization = `Bearer ${token}`; return config; }); api.interceptors.response.use( (res) => res, async (error) => { if (error.response?.status === 401) { // try refresh flow const newToken = await refreshToken(); if (newToken) { error.config.headers["Authorization"] = `Bearer ${newToken}`; return api.request(error.config); } } return Promise.reject(error); } ); export default api;

Service Wrapper Example

// src/api/services/user.service.ts import api from "../axios"; import { UserResponse } from "../../models/user"; export const getProfile = async (): Promise<UserResponse> => { try { const { data } = await api.get<UserResponse>("/user/profile"); return data; } catch (err) { // normalize error then rethrow or return fallback throw err; } };

API Error Debugging

When an API fails, go through this checklist quickly and in order. Log everything you change.

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 dataerror, and message shapes, backend contracts change.

Debugging tips

UX & Media Optimization

Images & Caching

// preloading example import FastImage from "react-native-fast-image"; FastImage.preload([{ uri: avatarUrl }, { uri: logoUrl }]);

Loaders And Perceived Performance

Network Strategies

Version Control (Git)

Best practices

Commit message style (example)

feat(auth): add refresh token flow to axios interceptor- add refreshToken helper - retry failed requests when 401 occurs - include unit tests

Branch strategy suggestions

Common Mistakes : Explanations & Quick Fixes

ts vs tsx

Git & GitLab mistakes

Avoid unnecessary re-renders in pagination / scrolling

iOS Pods: Don’t delete Pods/ or Podfile.lock immediately

Custom hooks for reusable logic

Function types: normal vs arrow vs callbacks

Before using AI for debugging

Quick Patterns & Snippets

Standard try/catch/finally pattern

try { setLoading(true); const res = await userService.getProfile(); setUser(res); } catch (err) { logger.error("getProfile failed", err); showToast(normalizeApiError(err)); } finally { setLoading(false); }

Small custom hook (API + cache example)

// useFetch.ts import { useState, useEffect } from "react"; import api from "../api/axios"; export const useFetch = <T>(url: string) => { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState<any>(null); useEffect(() => { let mounted = true; setLoading(true); api .get<T>(url) .then((res) => mounted && setData(res.data)) .catch((err) => mounted && setError(err)) .finally(() => mounted && setLoading(false)); return () => { mounted = false; }; }, [url]); return { data, loading, error }; };

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.