A modern, production-ready React Native boilerplate with TypeScript support, focusing on best practices, scalability, and developer experience. This boilerplate includes authentication flows, theming support, RTL handling, Firebase push notifications, React Native New Architecture support, and a robust project structure.
. โโโ android/ # Android native code โโโ ios/ # iOS native code โโโ src/ โ โโโ assets/ # Images, fonts, etc. โ โโโ components/ # Reusable UI components โ โโโ config/ # App configuration โ โโโ context/ # React Context providers โ โโโ helper/ # Helper services (notifications, etc.) โ โโโ hooks/ # Custom React hooks โ โโโ lang/ # i18n translations โ โโโ models/ # TypeScript interfaces โ โโโ navigation/ # Navigation setup โ โโโ redux/ # State management โ โโโ screens/ # Screen components โ โโโ styles/ # Global styles โ โโโ typings/ # Global TypeScript types โ โโโ utils/ # Utility functions โโโ patches/ # Patch files for dependencies โโโ vendor/ # Vendor files โโโ .eslintrc.js # ESLint configuration โโโ .prettierrc.js # Prettier configuration โโโ babel.config.js # Babel configuration โโโ metro.config.js # Metro bundler configuration โโโ tsconfig.json # TypeScript configuration โโโ package.json # Project dependencies
1. Clone the repository:
git clone <repository-url>
cd rn_boilerplate2. Install dependencies:
npm install
# or
yarn install3. iOS specific setup:
cd ios
pod install
cd ..4. Firebase Setup (for push notifications):
Android:
google-services.json file in android/app/iOS:
GoogleService-Info.plist file in ios/rn_boilerplate/cd ios && pod install after adding the file5. Start the application:
# Start Metro bundler
npm start
# or
yarn start
# iOS
npm run ios
# or
yarn ios
# Android
npm run android
# or
yarn androidThe app uses a context-based theme system:
// Usage in components
const { theme } = useTheme();
const colors = Colors[theme];
const styles = StyleSheet.create({
container: {
backgroundColor: colors.background
}
});Built-in i18next integration:
// Using translations
import { useTranslation } from 'react-i18next';
const { t } = useTranslation();
<TextComp text={t('WELCOME')} />// Using secure storage
import { secureStorage } from '@/utils/secureStorage';
await secureStorage.setItem('key', 'value');
const value = await secureStorage.getItem('key');
await secureStorage.setObject('userData', { name: 'John', age: 30 });
const userData = await secureStorage.getObject('userData');The app includes Firebase Cloud Messaging for push notifications:
// Request notification permissions and get FCM token
import { requestUserPermission } from '@/helper/notificationService';
// Call this in your App.tsx or main component
await requestUserPermission();Key Features:
This boilerplate is New Architecture Ready and supports:
Fabric (New Renderer):
TurboModules:
To enable New Architecture:
newArchEnabled=true in android/gradle.propertiesRCT_NEW_ARCH_ENABLED=1 in your Podfilecd ios && pod install && cd ..npm start - Start the Metro bundlernpm run ios - Run the iOS appnpm run android - Run the Android appnpm run lint - Run ESLintnpm run test - Run Jest testsThe app uses a Context-based theming system that allows for seamless switching between light and dark modes:
// src/context/ThemeContext.tsx
import React, { createContext, useState, useEffect, useContext } from 'react';
import { secureStorage } from '@/utils/secureStorage';
import { Colors, ThemeType } from '@/styles/colors';
// Usage in components
const { theme, toggleTheme } = useTheme();
const colors = Colors[theme];Key Features:
Using Theme Colors:
// Component example
import { useTheme } from '@/context/ThemeContext';
import { Colors } from '@/styles/colors';
const MyComponent = () => {
const { theme } = useTheme();
const colors = Colors[theme];
return (
<View style={{ backgroundColor: colors.background }}>
<Text style={{ color: colors.text }}>Hello World</Text>
</View>
);
};Fonts are loaded at application startup and made available through a centralized font family object:
// Font loading in App.tsx
const [loaded, error] = useFonts({
"Inter-Regular": require("./src/assets/fonts/Inter-Regular.ttf"),
"Inter-Bold": require("./src/assets/fonts/Inter-Bold.ttf"),
"Inter-Medium": require("./src/assets/fonts/Inter-Medium.ttf"),
"Inter-SemiBold": require("./src/assets/fonts/Inter-SemiBold.ttf"),
});
// Usage with fontFamily utility
import fontFamily from '@/styles/fontFamily';
const styles = StyleSheet.create({
title: {
fontFamily: fontFamily.bold,
fontSize: 18,
}
});Font Family Structure:
// src/styles/fontFamily.ts
export default {
regular: 'Inter-Regular',
medium: 'Inter-Medium',
semiBold: 'Inter-SemiBold',
bold: 'Inter-Bold',
};The app supports multiple languages with full RTL layout support using i18next:
// src/lang/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import 'intl-pluralrules';
import en from './en.json';
import ar from './ar.json';
i18n
.use(initReactI18next)
.init({
resources: {
en: { translation: en },
ar: { translation: ar }
},
lng: 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false
}
});
export default i18n;Translation Files Structure:
// en.json
{
"LOGIN": "Login",
"WELCOME_MESSAGE": "Welcome to NativeCraft"
}
// ar.json
{
"LOGIN": "ุชุณุฌูู ุงูุฏุฎูู",
"WELCOME_MESSAGE": "ู
ุฑุญุจูุง ุจู ูู NativeCraft"
}Using Translations:
// With the TextComp wrapper component
<TextComp text="LOGIN" />
// With variable substitution
<TextComp text="HELLO_USER" values={{ name: user.name }} />RTL Support:
// Custom hook for RTL detection
import { useTranslation } from 'react-i18next';
export default function useIsRTL() {
const { i18n } = useTranslation();
return i18n.language === 'ar';
}
// Usage in styles
const styles = StyleSheet.create({
container: {
flexDirection: isRTL ? 'row-reverse' : 'row',
textAlign: isRTL ? 'right' : 'left',
}
});The app uses a set of standardized components to ensure consistency:
TextComp - Text Component with i18n Support:
// Usage <TextComp text="WELCOME" /> <TextComp isDynamic text="Hello World" /> // Direct text without translation
ButtonComp - Customizable Button:
// Usage
<ButtonComp
text="LOGIN"
onPress={handleLogin}
variant="primary" // or "secondary", "outline", etc.
isLoading={isLoading}
/>TextInputComp - Custom Text Input:
// Usage
<TextInputComp
label="EMAIL"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
isPassword={false}
/>HeaderComp - App Header:
// Usage
<HeaderComp
title="PROFILE"
showBack={true}
onBackPress={() => navigation.goBack()}
/>WrapperContainer - Safe Area Wrapper:
// Usage
<WrapperContainer>
{/* Screen content */}
</WrapperContainer>The app uses Redux Toolkit for centralized state management:
// Store setup
// src/redux/store.ts
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;Reducers Structure:
// src/redux/reducers/index.ts
import { combineReducers } from '@reduxjs/toolkit';
import authReducer from './authSlice';
import appReducer from './appSlice';
const rootReducer = combineReducers({
auth: authReducer,
app: appReducer,
});
export default rootReducer;Actions Structure:
// src/redux/actions/index.ts
import * as authActions from './authActions';
import * as appActions from './appActions';
export default {
...authActions,
...appActions,
};Using Redux in Components:
// In a component
import { useDispatch, useSelector } from 'react-redux';
import actions from '@/redux/actions';
import { RootState } from '@/redux/store';
const Component = () => {
const dispatch = useDispatch();
const { user } = useSelector((state: RootState) => state.auth);
const handleLogin = async () => {
await dispatch(actions.login(credentials));
};
return (/* Component JSX */);
};The app uses a centralized API service based on Axios:
// src/config/api.ts
import axios from 'axios';
import { API_BASE_URL } from '@/config/constants';
const apiInstance = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
apiInstance.interceptors.request.use(
async (config) => {
// Add authorization token if available
const token = await getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor
apiInstance.interceptors.response.use(
(response) => response.data,
(error) => {
// Handle errors (401, 403, 500, etc.)
return Promise.reject(error);
}
);
export default apiInstance;API Actions:
// src/redux/actions/authActions.ts
import apiInstance from '@/config/api';
import { createAsyncThunk } from '@reduxjs/toolkit';
export const login = createAsyncThunk(
'auth/login',
async (credentials, { rejectWithValue }) => {
try {
const response = await apiInstance.post('/auth/login', credentials);
return response;
} catch (error) {
return rejectWithValue(error.response?.data || 'Login failed');
}
}
);The app uses Expo's SecureStore for encrypted storage:
// src/utils/secureStorage.ts
import * as SecureStore from 'expo-secure-store';
// Storage keys
export const STORAGE_KEYS = {
AUTH_TOKEN: 'AUTH_TOKEN',
USER_DATA: 'USER_DATA',
LANGUAGE: 'LANGUAGE',
THEME: 'THEME',
} as const;
type StorageKeyType = keyof typeof STORAGE_KEYS;
export const secureStorage = {
async setItem(key: StorageKeyType, value: string) {
try {
await SecureStore.setItemAsync(STORAGE_KEYS[key], value);
} catch (error) {
console.error('Error storing value:', error);
}
},
async getItem(key: StorageKeyType) {
try {
return await SecureStore.getItemAsync(STORAGE_KEYS[key]);
} catch (error) {
console.error('Error retrieving value:', error);
return null;
}
},
async removeItem(key: StorageKeyType) {
try {
await SecureStore.deleteItemAsync(STORAGE_KEYS[key]);
} catch (error) {
console.error('Error removing value:', error);
}
},
// Helper for storing objects
async setObject(key: StorageKeyType, value: object) {
try {
const jsonValue = JSON.stringify(value);
await this.setItem(key, jsonValue);
} catch (error) {
console.error('Error storing object:', error);
}
},
// Helper for retrieving objects
async getObject(key: StorageKeyType) {
try {
const jsonValue = await this.getItem(key);
return jsonValue ? JSON.parse(jsonValue) : null;
} catch (error) {
console.error('Error retrieving object:', error);
return null;
}
},
};The app uses react-native-svg and react-native-svg-transformer for SVG support:
// In components
import Logo from '@/assets/icons/logo.svg';
const Component = () => {
return (
<View>
<Logo width={100} height={100} fill={colors.primary} />
</View>
);
};The app uses a scaling utility for responsive dimensions:
// src/styles/scaling.ts
import { Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
// Guideline sizes are based on standard ~5" screen mobile device
const guidelineBaseWidth = 350;
const guidelineBaseHeight = 680;
export const horizontalScale = (size: number) => (width / guidelineBaseWidth) * size;
export const verticalScale = (size: number) => (height / guidelineBaseHeight) * size;
export const moderateScale = (size: number, factor = 0.5) =>
size + (horizontalScale(size) - size) * factor;Using Scale in Styles:
import { moderateScale } from '@/styles/scaling';
const styles = StyleSheet.create({
container: {
padding: moderateScale(16),
marginBottom: moderateScale(20),
},
title: {
fontSize: moderateScale(18),
},
});The app uses React Navigation 7.x with a structured approach:
// src/navigation/Routes.tsx
import React, { useEffect, useState } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import AuthStack from './stacks/AuthStack';
import MainStack from './stacks/MainStack';
import { useSelector } from 'react-redux';
import { RootState } from '@/redux/store';
const Routes = () => {
const { isAuthenticated } = useSelector((state: RootState) => state.auth);
return (
<NavigationContainer>
{isAuthenticated ? <MainStack /> : <AuthStack />}
</NavigationContainer>
);
};
export default Routes;Navigation Types:
// src/navigation/types.ts
export type AuthStackParamList = {
Login: undefined;
Signup: undefined;
OTPVerification: { email: string };
};
export type MainStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
};
export type TabParamList = {
HomeTab: undefined;
ProfileTab: undefined;
SettingsTab: undefined;
};This modular approach separates concerns clearly, making the codebase more maintainable:
The architecture is designed to scale with your application:
TypeScript provides: