https://github.com/linhnguyen-gt/new-react-native-zustand-react-query
A React Native boilerplate that follows Clean Architecture principles with Zustand for client state, React Query for server state, and modern development practices
https://github.com/linhnguyen-gt/new-react-native-zustand-react-query
clean-architecture hooks react-native react-query zustand
Last synced: 12 months ago
JSON representation
A React Native boilerplate that follows Clean Architecture principles with Zustand for client state, React Query for server state, and modern development practices
- Host: GitHub
- URL: https://github.com/linhnguyen-gt/new-react-native-zustand-react-query
- Owner: linhnguyen-gt
- Created: 2024-12-30T01:56:49.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-03-11T06:35:15.000Z (over 1 year ago)
- Last Synced: 2025-03-11T07:33:53.199Z (over 1 year ago)
- Topics: clean-architecture, hooks, react-native, react-query, zustand
- Language: TypeScript
- Homepage:
- Size: 826 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
🚀 React Native Modern Architecture
A modern React Native boilerplate with Zustand, React Query and best practices
Create a new project using our CLI: create-rn-project
### Core Libraries
### State Management & API
### UI & Styling
### Form & Validation
### Development & Testing
### Environment & Storage
### Development Tools
### Environment Support
## Key Features
### Architecture & State Management
- **Well-organized Architecture** with clear separation of concerns:
- Presentation Layer (UI/Screens/Hooks)
- Application Layer (State Management)
- Data Layer (API/Storage)
- Shared (Models/Utilities)
- **Modern State Management**
- Zustand for client-side state
- React Query for server-side state
- Async Storage for persistence
### Development Experience
- TypeScript for type safety
- Cross-platform (iOS & Android)
- NativeWind & Tailwind CSS for styling
- Jest setup for testing
- ESLint & Prettier configuration
### UI & Components
- Gluestack UI components
- Responsive design patterns
- Custom hooks and components
- Form handling with react-hook-form & zod
### Environment & Configuration
- Multi-environment support (Dev/Staging/Prod)
- Environment variable management
- Flavor/Scheme based builds
- Version management system
## Architecture Overview
The project follows a simplified but well-organized architecture to maintain:
- Separation of concerns
- Modularity
- Testability
- Maintainability
### Layer Responsibilities
1. **Presentation Layer** (`src/presentation/`)
- UI Components
- Screens
- Navigation
- Hooks for data access
2. **Application Layer** (`src/app/`)
- State Management (Zustand stores)
- Application-wide providers
3. **Data Layer** (`src/data/`)
- API services
- HTTP client
- Storage services
- External service integrations
4. **Shared Layer** (`src/shared/`)
- Models
- Types
- Constants
- Utility functions
## Quick Start
### Prerequisites
Make sure you have the following installed:
- Node.js (v20+)
- Yarn
- React Native CLI
- Xcode (for iOS)
- Android Studio (for Android)
- Ruby (>= 2.6.10)
- CocoaPods
### Installation
### Clone the repository\*\*
```bash
git clone https://github.com/linhnguyen-gt/new-react-native-zustand-react-query
cd new-react-native-zustand-react-query
```
## Environment Configuration
### Setup Environment
First, you need to run the environment setup script:
```bash
# Using npm
npm run env:setup
# Using yarn
yarn env:setup
```
This script will:
1. Set up dotenv-vault (optional)
2. Create environment files for all environments:
- `.env` (Development environment)
- `.env.staging` (Staging environment)
- `.env.production` (Production environment)
3. Configure necessary environment variables
### Environment Files Structure
Each environment file contains:
```bash
# Required Variables
APP_FLAVOR=development|staging|production
VERSION_CODE=1
VERSION_NAME=1.0.0
API_URL=https://api.example.com
# Optional Variables (configured during setup)
GOOGLE_API_KEY=
FACEBOOK_APP_ID=
# ... other variables
```
### Using Different Environments
```bash
# Development (default)
yarn android
yarn ios
# Staging
yarn android:stg
yarn ios:stg
# Production
yarn android:pro
yarn ios:pro
```
### Setup Steps for New Project
### iOS Configuration
1. **Podfile Configuration**
Add this configuration block to your Podfile:
```ruby
# configuration name environment
project 'NewReactNativeZustandRNQ',{
'Debug' => :debug,
'Release' => :release,
'Staging.Debug' => :debug,
'Staging.Release' => :release,
'Product.Debug' => :debug,
'Product.Release' => :release,
}
```
This configuration:
- Maps each build configuration to its corresponding mode (:debug or :release)
- Supports all environments (Dev, Staging, Product)
- Enables both Debug and Release builds for each environment
2. **Build Configurations**
Xcode should have these configurations set up:
- Staging.Debug/Release (Staging)
- Product.Debug/Release (Production)
- Debug/Release (Default)
3. **Version Management Script**
Add this script to Build Phase in Xcode:
.xcode.env.
```bash
# Determine APP_ENV based on CONFIGURATION
if [[ "${CONFIGURATION}" == *"Product"* ]]; then
export APP_ENV="production"
elif [[ "${CONFIGURATION}" == *"Staging"* ]]; then
export APP_ENV="staging"
fi
```
Build Phases -> Add Run Script -> Paste
```bash
# Get the environment from configuration name
echo "Debug: Raw CONFIGURATION value: ${CONFIGURATION}"
if [[ "${CONFIGURATION}" == *"Product"* ]]; then
ENV_FILE="${SRCROOT}/../.env.production"
echo "Debug: Matched Product configuration"
elif [[ "${CONFIGURATION}" == *"Staging"* ]]; then
ENV_FILE="${SRCROOT}/../.env.staging"
echo "Debug: Matched Staging configuration"
else
ENV_FILE="${SRCROOT}/../.env"
echo "Debug: Using default configuration"
fi
# Ensure INFOPLIST_FILE is set
if [ -z "$INFOPLIST_FILE" ]; then
echo "Error: INFOPLIST_FILE not set"
exit 0
fi
INFO_PLIST="${SRCROOT}/${INFOPLIST_FILE}"
echo "=== Environment Setup ==="
echo "CONFIGURATION: ${CONFIGURATION}"
echo "Using env file: ${ENV_FILE}"
echo "Info.plist path: ${INFO_PLIST}"
# Default values in case env file is missing
VERSION_CODE="1"
VERSION_NAME="1.0.0"
APP_NAME=""
# Try to read from env file if it exists
if [ -f "$ENV_FILE" ]; then
echo "Reading from env file..."
# Read VERSION_CODE
VERSION_CODE_LINE=$(grep "^VERSION_CODE=" "$ENV_FILE" || echo "")
if [ ! -z "$VERSION_CODE_LINE" ]; then
VERSION_CODE=$(echo "$VERSION_CODE_LINE" | cut -d'=' -f2 | tr -d '"' | tr -d ' ')
fi
# Read VERSION_NAME
VERSION_NAME_LINE=$(grep "^VERSION_NAME=" "$ENV_FILE" || echo "")
if [ ! -z "$VERSION_NAME_LINE" ]; then
VERSION_NAME=$(echo "$VERSION_NAME_LINE" | cut -d'=' -f2 | tr -d '"' | tr -d ' ')
fi
# Read APP_NAME
APP_NAME_LINE=$(grep "^APP_NAME=" "$ENV_FILE" || echo "")
if [ ! -z "$APP_NAME_LINE" ]; then
APP_NAME=$(echo "$APP_NAME_LINE" | sed 's/^APP_NAME=//' | sed 's/^"//' | sed 's/"$//')
fi
else
echo "Warning: Environment file not found, using default values"
fi
echo "Using versions - Code: $VERSION_CODE, Name: $VERSION_NAME, App Name: $APP_NAME"
# Update Info.plist if it exists
if [ -f "$INFO_PLIST" ]; then
echo "Updating Info.plist..."
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION_CODE" "$INFO_PLIST" || true
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION_NAME" "$INFO_PLIST" || true
if [ ! -z "$APP_NAME" ]; then
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $APP_NAME" "$INFO_PLIST" || true
fi
echo "Info.plist update completed"
else
echo "Warning: Info.plist not found at $INFO_PLIST"
fi
```
4. **Setup Steps for iOS**
- Copy the configuration block to your Podfile
- Run `pod install` to apply configurations
- Set up corresponding Build Configurations in Xcode
- Add the version management script to Build Phases
- Configure schemes to use appropriate configurations
### Android Configuration
1. **Product Flavors**
Add to app/build.gradle:
```gradle
flavorDimensions 'default'
productFlavors {
dev {
dimension 'default'
applicationId 'com.newreactnativezustandrnq'
resValue 'string', 'build_config_package', 'com.newreactnativezustandrnq'
def envFile = new File("${project.rootDir.parentFile}/.env")
if (envFile.exists()) {
def props = getVersionFromEnv(envFile)
versionCode props.code.toInteger()
versionName props.name
resValue "string", "app_name", props.appName
}
}
staging {
dimension 'default'
applicationId 'com.newreactnativezustandrnq.stg'
resValue 'string', 'build_config_package', 'com.newreactnativezustandrnq'
def envFile = new File("${project.rootDir.parentFile}/.env.staging")
if (envFile.exists()) {
def props = getVersionFromEnv(envFile)
versionCode props.code.toInteger()
versionName props.name
resValue "string", "app_name", props.appName
}
}
production {
dimension 'default'
applicationId 'com.newreactnativezustandrnq.production'
resValue 'string', 'build_config_package', 'com.newreactnativezustandrnq'
def envFile = new File("${project.rootDir.parentFile}/.env.production")
if (envFile.exists()) {
def props = getVersionFromEnv(envFile)
versionCode props.code.toInteger()
versionName props.name
resValue "string", "app_name", props.appName
}
}
}
def getVersionFromEnv(File envFile) {
def versionCode = '1'
def versionName = '1.0.0'
def appName = ''
envFile.eachLine { line ->
if (line.contains('=')) {
def (key, value) = line.split('=', 2)
if (key == 'VERSION_CODE') versionCode = value?.trim()?.replaceAll('"', '')
if (key == 'VERSION_NAME') versionName = value?.trim()?.replaceAll('"', '')
if (key == 'APP_NAME') appName = value?.trim()?.replaceAll('"', '')
}
}
println "Reading from ${envFile.path}"
println "VERSION_CODE: ${versionCode}"
println "VERSION_NAME: ${versionName}"
println "APP_NAME: ${appName}"
return [code: versionCode, name: versionName, appName: appName]
}
```
### Update package.json Scripts
```json
{
"scripts": {
"android": "npx expo run:android --device --variant devDebug --app-id com.newreactnativezustandrnq",
"android:stg": "APP_ENV=staging && npx expo run:android --device --variant stagingDebug --app-id com.newreactnativezustandrnq.stg",
"android:pro": "APP_ENV=production && npx expo run:android --device --variant productionDebug --app-id com.newreactnativezustandrnq.production",
"ios": "npx expo run:ios --device",
"ios:stg": "APP_ENV=staging npx expo run:ios --device --scheme Staging",
"ios:pro": "APP_ENV=production npx expo run:ios --device --scheme Product"
}
}
```
### Update .gitignore
```bash
.env*
.flaskenv*
!.env.project
!.env.vault
# Environment files
.env
.env.*
!.env.example
!.env.vault
```
### Version Management
The setup automatically manages app versions based on environment files:
- VERSION_CODE: Used for internal build numbering
- VERSION_NAME: Used for display version in stores
### Important Notes
- Never commit `.env` files to git (they are automatically added to .gitignore)
- Always commit `.env.example` and `.env.vault` (if using dotenv-vault)
- Share vault credentials with your team members if using dotenv-vault
## Project Structure
```
src/
├── app/ # Application Layer
│ ├── providers/ # App-wide providers
│ └── store/ # Zustand stores
│
├── data/ # Data Layer
│ ├── api/ # Raw API functions
│ ├── queries/ # React Query hooks
│ │ ├── queryKeys.ts # Centralized query keys
│ │ └── ... # Domain-specific query hooks
│ └── services/ # Infrastructure services
│ ├── httpClient/ # HTTP client configuration
│ └── ... # Other services
│
├── presentation/ # UI Layer
│ ├── components/ # Reusable UI components
│ ├── hooks/ # UI-related custom hooks
│ ├── screens/ # Screen components
│ └── navigation/ # Navigation setup
│
└── shared/ # Shared utilities
├── constants/ # Application constants
├── models/ # Data models
├── types/ # Type definitions
└── utils/ # Utility functions
```
## Development Tools
### Reactotron
For debugging, the project includes Reactotron integration. To use it:
1. Install Reactotron on your development machine
2. Run the following command for Android:
```bash
yarn adb:reactotron
```
## Code Style
The project uses ESLint and Prettier for code formatting. Run linting with:
```bash
yarn lint # Check for issues
```
To fix linting errors automatically, use:
```bash
yarn lint:fix # Fix automatic issues
```