An open API service indexing awesome lists of open source software.

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

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



react-native


typescript

### Core Libraries


expo
gluestack
react-navigation

### State Management & API


zustand
react-query
axios

### UI & Styling


nativewind
tailwindcss
vector-icons

### Form & Validation


react-hook-form
zod

### Development & Testing


eslint
prettier
jest

### Environment & Storage


dotenv
async-storage

### Development Tools


reactotron
reanimated

### Environment Support


ios
android


## 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
```