A modern, feature-rich React Native starter-kit and framework designed for rapid development and scalability.
This React Book perfectly describes everything, I started this project for. It discusses the same issues I’ve been ranting about in the past. Not just learning React, but understanding programming at a broader, more strategic level. Funny enough, this project existed before I run into that book, we must be soulmates.
Combining insights from this book with the RN/RN mini-framework can take your coding skills to the next level:
https://github.com/cloudstreet-dev/React-is-Awful
- Features
- Requirements
- Installation
- Project Structure
- Import Shortcuts
- Environment Configuration
- State Management
- Form Handling
- HTTP Client
- Authentication
- Styling and Theming
- Navigation
- Permissions
- Geolocation
- Connectivity
- Logging
- Storage
- App Provider
- Splash Screen
- App Lifecycle
- Contributing
- License
- 🚀 Ready-to-use architecture with best practices baked in
- 🧩 TypeScript for type safety and better developer experience
- 🌐 HTTP client with Axios for request/response handling
- 🔄 State management with Zustand and Immer
- 📝 Form handling with React Hook Form
- 🎨 Theming system with light/dark mode support
- 🔐 Authentication flow with token management
- 📱 Navigation using React Navigation
- 💾 Storage system with adapter pattern for MMKV and AsyncStorage
- 🔌 Environment configuration with react-native-dotenv
- 📍 Geolocation services with @react-native-community/geolocation
- 🔒 Permissions management with react-native-permissions
- 📶 Connectivity monitoring with @react-native-community/netinfo
- 📝 Logging system with react-native-logs
- 📅 Date handling with DayJS
- ✨ Animations with React Native Reanimated v4.0.1
- 🔄 AppProvider for bootstrapping the application
- 🚀 Splash Screen for initial loading experience
- 🎨 Icons and SVG Handling with pre-built icon components using react-native-svg
- 🔤 Custom fonts
- 🧪 Testing setup with Jest
- Node.js >= 18
- React Native CLI
- Xcode (for iOS development)
- Android Studio (for Android development)
- CocoaPods (for iOS dependencies)
-
Clone the repository:
git clone https://your-repository-url/rn-rn.git cd rn-rn -
Install dependencies:
npm install
-
Install iOS dependencies:
npm run pod:install
-
Set up environment variables:
cp .env.development.example .env.development cp .env.production.example .env.production
Edit the files to add your environment-specific variables.
-
Start the development server:
npm start
-
Run the app:
# For iOS npm run ios # For Android npm run android
rn-rn/
├── android/ # Android native code
├── ios/ # iOS native code
├── src/
│ ├── api/ # API clients and services
│ │ └── auth/ # Authentication API and strategies
│ ├── assets/ # Static assets (images, fonts, etc.)
│ ├── components/ # Reusable UI components
│ │ └── app/ # App-specific components
│ ├── exceptions/ # Custom error handling
│ ├── screens/ # Screen components
│ │ └── auth/ # Authentication screens
│ ├── state/ # State management (Zustand)
│ │ └── middlewares/ # State middlewares
│ ├── storage/ # Storage utilities
│ │ └── adapters/ # Storage adapters
│ ├── themes/ # Theme definitions
│ └── utils/ # Utility functions
├── types/ # TypeScript type definitions
├── App.tsx # Main App component
├── AppProvider.tsx # App initialization and providers
└── index.js # Entry point
RN/RN provides convenient import shortcuts to avoid deep relative imports:
// Instead of
import Component from '../../components/MyComponent';
// You can use
import Component from '@components/MyComponent';Available shortcuts:
'@src': './src',
'@components': './src/components',
'@state': './src/state',
'@app-types': './types',
To add or update shortcuts, modify babel.config.js and tsconfig.json.
RN/RN uses react-native-dotenv for environment configuration. There are two environments by default: development and production.
Environment-specific variables are stored in .env.{environment} files. Example:
# .env.development
APP_ENV=development
BASE_URL=https://api.dev.example.com
Usage in code:
import { APP_ENV, BASE_URL } from "@env";
console.log(`Current environment: ${APP_ENV}`);
console.log(`API base URL: ${BASE_URL}`);RN/RN uses Zustand for state management, following a slice pattern for better organization and type safety.
Each slice has a specific format:
const sliceKey = 'app' as const;
export interface AppStateData {
isReady: boolean,
}
export interface AppStateActions {
setAppReadyStatus: (isReady: boolean) => void;
set: (updater: (state: AppState) => void) => void;
}
export interface AppState {
[sliceKey]: AppStateData & AppStateActions;
}
export const appSlice: StateCreator<StoreState, [], [], AppState> = (set, get) => ({
[sliceKey]: {
isReady: false,
setAppReadyStatus: (isReady) =>
set((state) => ({
...state,
[sliceKey]: {
...state[sliceKey],
isReady: isReady,
},
})),
set: (fn) => set(produce(fn)),
},
});When adding a new slice, update the store.ts file to include it.
Usage:
import useStore from '@state/store';
// Access state
const isReady = useStore(state => state.app.isReady);
// Update state
const setAppReadyStatus = useStore(state => state.app.setAppReadyStatus);
setAppReadyStatus(true);RN/RN uses React Hook Form for efficient form management with minimal re-renders and easy validation.
import { useForm } from 'react-hook-form';
import Input from '@components/ui/forms/Input';
import { Button, View, Text } from 'react-native';
type FormData = {
email: string;
password: string;
};
function LoginForm() {
const { control, handleSubmit, formState: { errors } } = useForm<FormData>();
const onSubmit = (data: FormData) => {
console.log(data);
// Handle form submission
};
return (
<View>
<Input
name="email"
control={control}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
rules={{
required: 'Email is required',
pattern: {
value: /\S+@\S+\.\S+/,
message: 'Please enter a valid email',
},
}}
/>
<Input
name="password"
control={control}
placeholder="Password"
secureTextEntry
rules={{
required: 'Password is required',
minLength: {
value: 6,
message: 'Password must be at least 6 characters',
},
}}
/>
<Button title="Login" onPress={handleSubmit(onSubmit)} />
</View>
);
}React Hook Form provides built-in validation with error messages:
// Example validation rules
const validationRules = {
required: 'This field is required',
minLength: {
value: 6,
message: 'Minimum 6 characters'
},
maxLength: {
value: 20,
message: 'Maximum 20 characters'
},
pattern: {
value: /\S+@\S+\.\S+/,
message: 'Invalid format'
},
validate: (value) => value === 'correct' || 'Invalid value'
};The Input component supports custom children for advanced input types:
import { useForm } from 'react-hook-form';
import Input from '@components/ui/forms/Input';
import { View, TextInput } from 'react-native';
function CustomInputExample() {
const { control } = useForm();
return (
<View>
{/* With custom child component */}
<Input
name="customField"
control={control}
rules={{ required: 'This field is required' }}
>
<TextInput
style={{ height: 100, textAlignVertical: 'top' }}
multiline
placeholder="Enter long text here..."
/>
</Input>
</View>
);
}For more advanced usage, refer to the React Hook Form documentation.
RN/RN provides a wrapper around Axios for making HTTP requests. API calls should be grouped into *.api.ts files under the api folder:
export const useGetApiStatus = () => {
const { response, isLoading, error, get, reset } = useGetRequest() as GetRequestState<{status: 'connected' | null}>;
const request = async (parameters: {} = {}) => {
return await get('/status', parameters);
};
return { response, isLoading, error, request, reset };
};For more complex operations, add a handle function:
export const useFollowUser = () => {
const { isLoading, error, post, reset } = usePostRequest() as PostRequestState<FollowUnfollowResponse>;
const setUserData = useStore((state: UsersState) => state.users.set);
const authUserId = useStore((state: AuthState) => state.auth.userId);
const request = async (id: number) => {
return await post(`/v2/${id}/follow`);
};
const handle = async (id: number) => {
const response = await request(id);
setUserData((state) => {
if(!authUserId) return;
state.users.data[id].followings.push(response.data);
});
return response;
};
return { isLoading, error, request, handle, reset };
};Usage:
const { handle, isLoading } = useFollowUser();
handle(user?.id).then((response) => {
console.log(response);
});Authentication is implemented with token management and various authentication strategies. For detailed information, see Authentication Documentation.
RN/RN includes a comprehensive theming system with support for light and dark modes. The system provides a consistent way to style your application with automatic theme switching based on device preferences.
- Light and Dark Mode Support: Themes automatically update based on system preferences or can be manually set
- Utility Classes: Bootstrap-style utility classes for consistent styling across your app
- Responsive Typography: Variable typography with predefined heading styles and text decorations
- Flexible Grid System: 12-column grid system with responsive layouts
- Spacing Utilities: Consistent spacing with margin and padding helpers
- Color System: Contextual colors for primary, secondary, success, danger, etc.
- Component Styles: Pre-styled components like buttons, cards, and inputs
- Global Access: Access theme properties both inside and outside of React components
Theme files follow the naming convention: [name].[scheme].theme.ts (e.g., default.light.theme.ts, default.dark.theme.ts).
Each theme consists of:
- Colors: Base colors, contextual colors, and theme-specific colors
- Styles: Core component styles and utility helper styles
- Spacing: Consistent spacing values for margins, padding, and gaps
// Access the theme in components
import { useTheme } from '@src/themes/theme.context';
import { View, Text } from 'react-native';
function MyComponent() {
const { Theme } = useTheme();
return (
<View style={[Theme.styles.Bg]}>
<Text style={[Theme.styles.Text]}>Hello World</Text>
</View>
);
}
// Or use the global Theme function outside of components
import { Theme } from '@src/themes/theme.context';
const backgroundColor = Theme().colors.background;The Theme.styles.Bg and Theme.styles.Text styles automatically adapt to the current theme:
// This will use light or dark background/text colors based on the active theme
<View style={[Theme.styles.Bg]}>
<Text style={[Theme.styles.Text]}>Adaptive text</Text>
</View>// Headings
<Text style={Theme.styles.H1}>Heading 1</Text>
<Text style={Theme.styles.H2}>Heading 2</Text>
// ... H3, H4, H5, H6
// Text styles
<Text style={[Theme.styles.Text, Theme.styles.text.center]}>Centered text</Text>
<Text style={[Theme.styles.Text, Theme.styles.text.strong]}>Bold text</Text>
<Text style={[Theme.styles.Text, Theme.styles.text.muted]}>Muted text</Text>
<Text style={[Theme.styles.Text, Theme.styles.text.small]}>Small text</Text>
<Text style={[Theme.styles.Text, Theme.styles.text.underline]}>Underlined text</Text>
<Text style={[Theme.styles.Text, Theme.styles.text.strike]}>Strike-through text</Text>
// Special text components
<Text style={Theme.styles.Lead}>Lead paragraph</Text>
<Text style={Theme.styles.Link}>Link text</Text>
<Text style={Theme.styles.Code}>Code snippet</Text>// Basic row with equal columns
<View style={Theme.styles.row}>
<View style={Theme.styles.col.grow}>
<Text>Column 1</Text>
</View>
<View style={Theme.styles.col.grow}>
<Text>Column 2</Text>
</View>
</View>
// 12-column grid system
<View style={Theme.styles.row}>
<View style={Theme.styles.col.col4}>
<Text>4 columns</Text>
</View>
<View style={Theme.styles.col.col8}>
<Text>8 columns</Text>
</View>
</View>
// Alignment options
<View style={[Theme.styles.row, Theme.styles.justifyContent.center]}>
<Text>Centered content</Text>
</View>// Margins (m = margin, t/r/b/l/x/y = direction, 0-5 = size)
<View style={Theme.styles.m3}>
<Text>Margin 3 on all sides</Text>
</View>
<View style={Theme.styles.mt2}>
<Text>Margin top 2</Text>
</View>
<View style={Theme.styles.mx4}>
<Text>Horizontal margin 4</Text>
</View>
// Padding (p = padding, t/r/b/l/x/y = direction, 0-5 = size)
<View style={Theme.styles.p3}>
<Text>Padding 3 on all sides</Text>
</View>
<View style={Theme.styles.py2}>
<Text>Vertical padding 2</Text>
</View>
// Gap
<View style={Theme.styles.g2}>
<Text>Gap 2 between children</Text>
</View>For more detailed documentation, see Themes Documentation.
RN/RN uses React Navigation for screen navigation:
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './HomeScreen';
import ProfileScreen from './ProfileScreen';
// Create a stack navigator
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}Navigation types are defined in /types/navigation.types.ts.
RN/RN uses react-native-svg for rendering SVG icons and graphics. The boilerplate includes a set of pre-built icon components in src/assets/icons/icons.tsx.
Icons are implemented as React components that accept standardized props:
import { ChevronLeftIcon } from '@src/assets/icons/icons';
// With default props (height: 28, width: 28, color: theme text color)
<ChevronLeftIcon />
// With custom props
<ChevronLeftIcon height={24} width={24} color="#FF0000" />All icons accept the following props through the IconProps interface:
| Prop | Type | Default | Description |
|---|---|---|---|
| height | number | 28 | The height of the icon |
| width | number | 28 | The width of the icon |
| color | string | Theme().colors.text | The color of the icon |
| ...restProps | any | - | Any additional props to pass to the SVG component |
To add a new icon:
- Import the SVG paths or elements from your SVG file
- Create a new component following the pattern of existing icons
- Export the component with appropriate default props
export const NewIcon = ({height = 28, width = 28, color = Theme().colors.text, ...restProps}: IconProps) => (
<Svg
width={width}
height={height}
fill="none"
viewBox="0 0 24 24"
{...restProps}
>
<Path
d="..." // Your SVG path data
fill={color}
/>
</Svg>
);The ScreenHeader component provides a consistent header across screens with navigation controls and title.
import { ScreenHeader } from '@components/ui/ScreenHeader';
// Basic usage with default back button
<ScreenHeader title="Screen Title" />
// Custom right icon with action
<ScreenHeader
title="Screen Title"
RightIcon={SearchIcon}
onRightComponentPress={() => console.log('Search pressed')}
/>
// Custom styling
<ScreenHeader
title="Screen Title"
style={{ backgroundColor: 'black' }}
textColor="white"
/>| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | (required) | The title text to display in the header |
| style | ViewStyle | undefined | Custom styles for the header container |
| textColor | string | Theme.colors.text | Color for the title and icons |
| LeftIcon | IconComponent | null | ChevronLeftIcon | Icon component for the left side (usually back button) |
| RightIcon | IconComponent | undefined | Icon component for the right side (optional) |
| onLeftComponentPress | () => void | navigation.goBack() | Function to call when left icon is pressed |
| onRightComponentPress | () => void | null | Function to call when right icon is pressed |
RN/RN uses react-native-permissions for permission management.
To add new permissions:
- iOS: Update
ios/Podfileandios/YourApp/Info.plistwith the required permission entries - Android: Update
android/app/src/main/AndroidManifest.xmlwith the required permission entries
For a comprehensive understanding of the permission flow, refer to the permission flow diagram.
RN/RN implements @react-native-community/geolocation for location services.
Important notes:
- Always access location data through the app state, which is automatically updated
- Never import and use the geolocation plugin directly, as running it twice can cause issues
- Start the location watcher using
watchLocation.start()after checking permissions
Usage:
// Access location data from state
const location = useStore(state => state.app.device.location);
// Start watching location (after checking permissions)
import { watchLocation } from '@src/utils/geolocation';
watchLocation.start();RN/RN monitors network connectivity using @react-native-community/netinfo.
The connectivity status is automatically updated in the app state:
// Access connectivity status from state
const connectivity = useStore((state) => state.app.connectivity);
// Check if connected
if (connectivity.isConnected) {
// Device has an active network connection
}
// Check if online
if (connectivity.isOnline) {
// Device has internet access
}RN/RN includes a logging system based on react-native-logs with improved formatting and grouping capabilities:
import { log, logAPI } from '@src/utils/logger';
// General logging
log.debug('Debug message');
log.info('Info message');
log.warn('Warning message');
log.error('Error message');
// API logging group
logAPI.debug('API request', { url, method, data });
logAPI.info('API response', { status, data });RN/RN provides a flexible storage system using the Adapter Pattern, allowing you to easily switch between different storage providers:
import { storage } from '@src/storage/Storage';
// Store data
storage.setItem('MY_KEY', { data: 'example' });
// Retrieve data
const data = storage.getItem('MY_KEY');
// Remove data
storage.removeItem('MY_KEY');
// Clear all data
storage.clear();The storage system includes adapters for:
- MMKV (high-performance, synchronous storage)
- AsyncStorage (asynchronous storage with Promise-based API)
For detailed documentation on how to use the storage system and create custom adapters, see the Storage Documentation.
RN/RN uses DayJS for date manipulation and formatting. DayJS is a lightweight alternative to Moment.js with a similar API.
import dayjs from 'dayjs';
// Get current date/time
const now = dayjs();
// Format dates
const formattedDate = dayjs().format('YYYY-MM-DD HH:mm:ss');
// Parse dates
const date = dayjs('2025-08-01');
// Date calculations
const tomorrow = dayjs().add(1, 'day');
const lastWeek = dayjs().subtract(7, 'day');
// Date comparisons
const isBefore = dayjs('2025-01-01').isBefore(dayjs('2025-12-31'));
const isSame = dayjs('2025-01-01').isSame(dayjs('2025-01-01'));RN/RN comes with the Ubuntu font family pre-configured. The font files are located in the assets/fonts directory.
To use the custom fonts in your styles:
import { DEFAULT_FONT } from '@src/constants';
const styles = StyleSheet.create({
text: {
fontFamily: DEFAULT_FONT,
fontSize: 16,
},
boldText: {
fontFamily: DEFAULT_FONT,
fontWeight: 'bold',
},
});To add additional custom fonts:
- Add your font files to the
assets/fontsdirectory - Link the fonts using:
npx react-native-asset
- For iOS, you may need to add the font to the Info.plist file
- For Android, the fonts should be automatically linked
For more details, see the React Native documentation on custom fonts.
The AppProvider is a crucial component that bootstraps the application and manages its initialization process. It wraps the entire application and handles various setup tasks before the main UI is displayed.
import { AppProvider } from './AppProvider';
function App() {
return (
<AppProvider>
{/* Your app content */}
</AppProvider>
);
}- Application Bootstrapping: Initializes essential services and data before the app becomes interactive
- State Management: Sets the app's ready status when initialization is complete
- System Monitoring: Subscribes to color scheme and connectivity changes
- Splash Screen Control: Works with the isReady state to control when the splash screen is dismissed
The AppProvider uses React's Context API to provide initialization services to the entire application:
export const AppProvider = ({ children }: { children: ReactNode }) => {
const setAppReadyStatus = useStore(state => state.app.setAppReadyStatus);
useColorSchemes(); // Initialize and subscribe to color scheme updates
useConnectivity(); // Subscribe to network/internet connection updates
useEffect(() => {
const bootstrap = async () => {
// Perform initialization tasks here
// For example: load user data, check authentication, etc.
// When everything is ready, set isReady to true
setAppReadyStatus(true);
};
bootstrap();
}, []);
return <>{children}</>;
};The Splash Screen is displayed during the application's initialization phase, providing visual feedback to users while the app loads. It's automatically shown when the app starts and dismissed once the AppProvider sets the isReady state to true.
import SplashScreen from '@components/app/SplashScreen.component';
function App() {
const isReady = useStore(state => state.app.isReady);
return (
<AppProvider>
{!isReady ? (
<SplashScreen />
) : (
<View>
{/* Main application UI */}
</View>
)}
</AppProvider>
);
}The SplashScreen component is a simple loading screen with an activity indicator:
const SplashScreen = () => {
const { Theme } = useTheme();
return (
<View style={[Theme.styles.container.flex, Theme.styles.container.center]}>
<Text style={[Theme.styles.Text, Theme.styles.mb4]}>Loading...</Text>
<ActivityIndicator size="large" color={Theme.colors.primary}/>
</View>
);
};The AppProvider and Splash Screen work together to create a smooth startup experience:
- When the app launches, the AppProvider begins its initialization process
- While initialization is in progress,
isReadyis false, so the Splash Screen is displayed - The AppProvider performs necessary setup tasks (loading data, checking authentication, etc.)
- Once initialization is complete, the AppProvider sets
isReadyto true - This state change triggers a re-render, replacing the Splash Screen with the main application UI
This pattern ensures that users see a loading indicator until the app is fully ready to use, providing a better user experience by avoiding partially loaded or non-functional UI states.
╔══════════════════════════════════════════════════════════════════╗
║ 📱 React Native App Boot ║
╚══════════════════════════════════════════════════════════════════╝
┌───────────────────────────────┐
│ 📦 Native Code (iOS/Android)│
└────────────┬──────────────────┘
│ Launches native app shell
▼
┌─────────────────────────────┐
│ 🟦 JavaScript Bridge │
└────────────┬────────────────┘
│ Loads JS bundle (from metro or release bundle)
▼
┌─────────────────────────────┐
│ 🚀 App Entry: index.js │
└────────────┬────────────────┘
│
┌────────────────┴──────────────────────┐
▼ ▼
┌─────────────────────────────────┐ ┌──────────────────────────┐
│ AppRegistry.registerComponent() │ │ App.tsx (Root Component)│
└─────────────────────────────────┘ └────────────┬─────────────┘
▼
┌───────────────────────────┐
│ <AppProvider> │
│ <ThemeProvider> │
│ <NavigationContainer> │
└────────────┬──────────────┘
│
Runs first render ▼
┌───────────────────────────┐
│ React Renders Tree │
│ (Virtual DOM creation) │
└────────────┬──────────────┘
▼
┌────────────────────────────────────┐
│ 🎯 useEffect (on mount) │
│ (e.g. bootstrap(), listeners) │
└─────────────────┬──────────────────┘
▼
┌─────────────────────────────────┐
│ 🔁 React state updates │
│ - from stores/hooks/etc │
│ - triggers re-renders │
└─────────────────┬───────────────┘
▼
┌──────────────────────────────────────┐
│ Re-render updated subtree │
└─────────────────┬────────────────────┘
▼
┌────────────────────────────────────┐
│ React reconciles + updates native │
│ views via Fabric/Bridge │
└────────────────────────────────────┘
╔══════════════════════════════════════════════════════════════════╗
║ 💬 Events / Listeners during Runtime ║
╚══════════════════════════════════════════════════════════════════╝
User interaction, API responses, animations, system events like:
- AppState (foreground/background)
- Dimensions (rotation)
- Appearance (color scheme)
- NetInfo (connectivity)
- Push notifications
- Timers, gestures, scroll, etc
▼
Trigger JS handlers (callbacks, hooks) → update state → re-render
╔══════════════════════════════════════════════════════════════════╗
║ 🔚 App Teardown / Background ║
╚══════════════════════════════════════════════════════════════════╝
When user exits app or it gets backgrounded:
- useEffect cleanups run
- Subscriptions removed
- Native views paused/unmounted
React Native keeps JS engine alive in background unless killed.
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'Add my feature' - Push to the branch:
git push origin feature/my-feature - Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
