React Native is a powerful tool that allows you to develop both iOS and Android applications with a single codebase. However, a successful app development process is not just about using components. You also need to master several core topics, such as dependency management, performance optimization, data management, form validation, native modules, and finally, the app publishing process.
Building on the Basics: In addition to React Native's basic features like useState
, useEffect
, useMemo
, useCallback
, and the Context API, you should also be proficient in more complex structures such as Redux, React Navigation, AsyncStorage, MMKV, and performance optimization techniques.
Native Modules and Bridging: React Native may not always be sufficient; therefore, when necessary, you should be able to write native modules with Java (for Android) and Objective-C/Swift (for iOS).
Performance Management: To control the re-rendering of components, hooks like memo
, useCallback
, and useMemo
should be effectively utilized. Additionally, optimizations should be considered when working with lists, such as using FlatList
.
Animations: To ensure smooth and high-performance animations in React Native, libraries like Reanimated and Gesture Handler should be well understood.
App Startup Time: Techniques for shortening the app's startup time and loading large modules using lazy loading should be known.
Redux / Context API / React Query: In large-scale projects, global state management solutions like Redux or React Query should be handled, while smaller projects may benefit from using the Context API.
State Persistency: In cases where data needs to be continuously stored, state management should be maintained with tools like Redux Persist, MMKV, or AsyncStorage.
In this article, I will also dive into tools and libraries such as Watchman, Gemfile, Metro, react-native-gesture-handler
, react-native-gradle-plugin
, react-native-mmkv
, react-native-webview
, @tanstack/react-query
, Axios, Ky, react-hook-form
, Zod, and TypeScript, explaining them in detail with examples.
1. React Native Development Environment Setup
a. What is Watchman?
Watchman is a tool developed by Facebook that monitors file changes and triggers specific commands when those changes occur. In React Native projects, it is used to enable automatic reload (hot-reload). When you make changes to a file, Watchman detects the modification and updates the project accordingly.
brew install watchman
How Does Watchman Work?
Watchman monitors file system changes and automatically restarts the project based on these changes. This speeds up the development process and eliminates the need for manual restarts.
b. What is a Gemfile?
A Gemfile is a file used to manage Ruby dependencies. You can use a Gemfile to integrate tools like Fastlane and Cocoapods into your project. This provides fast and efficient dependency management, especially for iOS applications.
source "https://rubygems.org"
gem "fastlane"
gem "cocoapods"
This file allows you to install the correct versions of Fastlane and Cocoapods. By running the bundle install
command, you can add these dependencies to your project.
c. What is Metro?
Metro is React Native’s bundler tool. It combines all your JavaScript code into a single file and optimizes it. Metro compiles project code quickly and provides advanced error reporting.
Role of Metro:
- Hot Reloading: Reflects code changes instantly in the app.
- Code Bundling: Combines JavaScript files to improve performance.
- Error Reporting: Makes errors more readable during development.
Metro enhances the performance of React Native projects and speeds up the development process.
2. Dependency Management in React Native: Working with Cocoapods and Gradle
Dependency management is important for both platforms in React Native projects: Cocoapods for iOS and Gradle for Android.
What is Cocoapods and How is it Used?
Cocoapods provides dependency management for iOS applications. You can add third-party libraries to your project by adding the necessary dependencies to the Podfile
and running the pod install
command.
Sample Podfile:
platform :ios, '12.0'
target 'YourApp' do
pod 'Firebase', '~> 6.0'
pod 'ReactNativeGestureHandler', :path => '../node_modules/react-native-gesture-handler'
end
Installation Command:
pod install
Cocoapods makes it easy to add and manage third-party libraries in iOS projects. It provides version control for any library and manages in-app dependencies.
What is Gradle and How is it Used in Android Projects?
Gradle is the dependency management and build system for Android projects. The react-native-gradle-plugin
simplifies and optimizes these processes. In Android projects, you can define and include third-party libraries using the build.gradle
file.
Example build.gradle
:
dependencies {
implementation 'com.google.firebase:firebase-analytics:17.2.0'
implementation 'com.facebook.react:react-native:+'
}
The react-native-gradle-plugin
adapts Gradle for React Native projects and speeds up build processes. It also provides the necessary settings to ensure the proper signing and optimization of the application.
3. Native Modules and Bridging
React Native handles many tasks using JavaScript; however, in some cases, you may need to write native modules. Particularly for platform-specific tasks (like Bluetooth, NFC, or camera access), native modules are written on the Android (Java) and iOS (Objective-C/Swift) sides and bridged with React Native.
a. Creating a Native Module: Android
Java is used to create a native module for Android. For example, to write a simple module that fetches the battery level of an Android device, you can follow these steps:
Step 1: Creating a Java File
To create a native module for Android, create a Java file at the path android/app/src/main/java/com/yourproject/
. In this example, the file name will be BatteryModule.java
.
package com.yourproject;
import android.os.BatteryManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
public class BatteryModule extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
BatteryModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
@NonNull
@Override
public String getName() {
return "BatteryModule";
}
@ReactMethod
public void getBatteryLevel(Promise promise) {
try {
Intent batteryIntent = reactContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int level = batteryIntent != null ? batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) : -1;
int scale = batteryIntent != null ? batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1) : -1;
float batteryLevel = level / (float) scale;
promise.resolve(batteryLevel);
} catch (Exception e) {
promise.reject("Error", e);
}
}
}
Explanation:
We have created a Java class named BatteryModule
, and this class will be bridged to React Native as a native module.
We defined a method called getBatteryLevel
. This method retrieves the battery level of the device and returns it to the React Native side through a Promise.
Step 2: Registering the Module with React Native
Go to the MainApplication.java
file and register the native module with React Native.
import com.yourproject.BatteryModule;
public class MainApplication extends Application implements ReactApplication {
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new BatteryPackage()
);
}
}
To create the module package, we add a class named
package com.yourproject;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BatteryPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new BatteryModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Explanation:
BatteryPackage
is used to link the BatteryModule
class to React Native. By adding this package to MainApplication
, the bridging process is completed.
b. Creating a Native Module: iOS
On the iOS side, you can write a native module using Swift or Objective-C. For example, to retrieve the battery level of iOS devices, you can use the following code:
Step 1: Creating a Swift File
In Xcode, we create a Swift file named BatteryModule.swift
.
import Foundation
import UIKit
@objc(BatteryModule)
class BatteryModule: NSObject {
@objc
func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
UIDevice.current.isBatteryMonitoringEnabled = true
let batteryLevel = UIDevice.current.batteryLevel
if batteryLevel >= 0 {
resolve(batteryLevel)
} else {
let error = NSError(domain: "", code: 200, userInfo: nil)
reject("no_battery_info", "Could not fetch battery level", error)
}
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
}
Explanation:
We create a class named BatteryModule
. This class retrieves the device's battery level.
The getBatteryLevel
method returns the battery level using a Promise.
Step 2: Bridging the Module
To bridge BatteryModule
to React Native, we create an Objective-C bridging file named BatteryModule.m
:
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(BatteryModule, NSObject)
RCT_EXTERN_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
@end
This is used to bridge the module we wrote in Swift to React Native.
c. Usage on the React Native Side
Now that we have written and bridged the native modules for both Android and iOS, we can use these modules on the React Native side as follows:
import { NativeModules, Button, Text, View } from 'react-native';
import React, { useState } from 'react';
const { BatteryModule } = NativeModules;
const App = () => {
const [batteryLevel, setBatteryLevel] = useState(null);
const getBatteryLevel = async () => {
try {
const level = await BatteryModule.getBatteryLevel();
setBatteryLevel(level);
} catch (error) {
console.error(error);
}
};
return (
<View>
<Text>Battery Level: {batteryLevel !== null ? ${batteryLevel * 100}% : N/A}</Text>
<Button title="Get Battery Level" onPress={getBatteryLevel} />
</View>
);
};
export default App;
Explanation:
By calling the BatteryModule.getBatteryLevel()
function, we retrieve the device's battery level and display it on the screen using useState
.
With this method, you can add platform-specific features to React Native projects. Native modules provide more direct access to device hardware and can help improve the performance of your projects.
4. Performance Optimization: Developing Faster Applications
In React Native projects, performance is a critical concern. Ensuring that your application is fast and smooth for users is one of the key factors for a successful mobile application. You can use the following optimization methods to enhance performance in large and complex projects:
a. Optimized Listings with FlatList
If your application manages large data lists, using FlatList
can significantly improve performance. FlatList
only renders the items visible on the screen, which reduces unnecessary memory usage.
<FlatList
data={data}
renderItem={({ item }) => <Text>{item.title}</Text>}
keyExtractor={item => item.id}
/>
Additionally, if you want to use a faster and more performant FlatList
, you can check out this library:
Github Repo: Shopify Flash List
b. Preventing Unnecessary Renders with Memoization and useCallback
In React Native projects, you can use the useMemo
and useCallback
hooks to prevent unnecessary renders, especially in large components. These hooks prevent components from re-rendering unless certain changes occur.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
These methods especially improve performance on screens with many components, making your application respond faster.
c. Performant Animations with Reanimated and Gesture Handler
In React Native, animations can sometimes lead to performance issues. Therefore, libraries like React Native Reanimated and Gesture Handler are used for high-performance animations and gestures.
import { useSharedValue, withSpring } from 'react-native-reanimated';
const progress = useSharedValue(0);
progress.value = withSpring(1);
These libraries ensure that performance is maintained, especially when working with heavy animations and gestures.
Github Repo: React Native Reanimated
5. Performance Optimization: High-Performance Storage with MMKV
When performing data storage operations in your application, you need a high-performance storage mechanism. react-native-mmkv
, developed by Facebook, is an optimized storage solution for React Native. MMKV allows you to handle large data sets quickly.
How to Use MMKV?
Installation:
Example: Storing and Retrieving User Data
To store and later retrieve simple user data using MMKV, you can follow these steps:
import { MMKV } from 'react-native-mmkv';
const userData = {
id: 1,
name: "Abdulnasır olcan",
email: "abdulnasir.olcan@example.com",
isLoggedIn: true,
};
const storage = new MMKV();
storage.set('user', JSON.stringify(userData));
const userString = storage.getString('user');
if (userString) {
const user = JSON.parse(userString);
console.log('user:', user);
} else {
console.log('Error');
}
Key Points:
-
Data Saving: We save the data using
storage.set('key', value)
. Here, we store the user data (userData
) in JSON format under theuser
key. -
Data Reading: We retrieve the data using
storage.getString('key')
. To convert the data back to JSON format, we useJSON.parse()
. -
Error Handling: When fetching the data, if the data is empty (for example, if it was never saved or has been deleted), it will return
null
. Therefore, we check whether the data exists or not.
Example: Updating and Deleting Data
Updating or deleting data stored in MMKV is also very simple. You can use the examples below for updating and deleting data.
Updating Data:
const updatedUserData = {
...userData,
name: "Abdulnasır olcan",
};
storage.set('user', JSON.stringify(updatedUserData));
const updatedUserString = storage.getString('user');
if (updatedUserString) {
const updatedUser = JSON.parse(updatedUserString);
console.log('update user:', updatedUser);
}
Example: Deleting Data
// Depolanan kullanıcı verisini siliyoruz
storage.delete('user');
// Sildiğimiz veriyi kontrol ediyoruz
const deletedUserString = storage.getString('user');
if (!deletedUserString) {
console.log('Kullanıcı verisi başarıyla silindi.');
}
MMKV is a fast and secure local data storage solution used to enhance your application's performance. It allows you to quickly store user data, session information, or other important information.
Github Repo: React Native mmkv
6. Integration with Web Content: react-native-webview
You can use the react-native-webview
component to display web content in your application. This component renders web pages using the native browser engine.
How to Use WebView?
Installation:
npm install react-native-webview
Example Usage:
import { WebView } from 'react-native-webview';
const MyWeb = () => (
<WebView source={{ uri: 'https://reactnative.dev/' }} />
);
WebView allows you to easily display external content that you want to integrate into your application. This is especially useful for in-app payment pages or viewing external documents.
Github Repo: React Native Webview
7. Data Management: Data Fetching with @tanstack/react-query
When your application exchanges data with APIs, you can simplify data fetching and caching by using @tanstack/react-query
. React Query manages API requests and provides automatic retry and caching functionalities.
Fetching Data with React Query:
Installation:
npm install @tanstack/react-query
Example Usage:
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
const fetchData = async () => {
const { data } = await axios.get('https://api.example.com/data');
return data;
};
const MyComponent = () => {
const { data, isLoading } = useQuery(['data'], fetchData);
if (isLoading) return <Text>Loading...</Text>;
return <Text>{data.title}</Text>;
};
React Query allows you to manage data requests more effectively. Additionally, with react-query-devtools
, you can visually monitor and analyze the state of your queries.
Github Repo: TanStack Query
8. Navigation: Screen Transitions with react-navigation
In React Native applications, react-navigation
is used for transitioning between screens. It is ideal for navigating between multiple screens, opening modals, and handling stacked navigation.
Using react-navigation:
Installation:
npm install @react-navigation/native
npm install @react-navigation/stack
Example Usage:
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const MyStack = () => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
Navigation is a critical component in modern mobile applications, and react-navigation
is one of the most commonly used solutions for screen transitions.
Github Repo: React Navigation
9. API Requests: Fetching Data with Axios and Ky
The two most commonly used libraries for managing API requests are Axios and Ky. Both provide simple and efficient solutions for making HTTP requests.
Making an API Request with Axios:
Installation:
npm install axios
Example Usage:
axios.get('https://api.example.com/data')
.then(response => console.log(response.data))
.catch(error => console.error(error));
Github Repo: Axios
Making an API Request with Ky:
Installation:
npm install ky
Example Usage:
import ky from 'ky';
ky.get('https://api.example.com/data').json()
.then(data => console.log(data));
Github Repo: Ky
Both libraries can be used for data exchange with APIs, and each offers different advantages. Axios provides a broader API and more flexibility, while Ky is lighter and has a more modern structure.
10. Form Management and Validation: Powerful Forms with react-hook-form and Zod
For form management and validation, react-hook-form
and Zod
can be used. These tools allow you to manage form data more securely and flexibly.
Form Management with react-hook-form:
Installation:
npm install react-hook-form
Example Usage:
import { useForm } from 'react-hook-form';
const MyForm = () => {
const { register, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
<input type="submit" />
</form>
);
};
Github Repo: React Hook Form
Form Validation with Zod:
Installation:
npm install zod
Example Usage:
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters long'),
});
schema.parse({ name: 'Abdulnasır olcan' });
schema.parse({ name: 'A' });
Github Repo: Zod
While react-hook-form
is optimized for managing and validating forms, Zod
makes form validation even more powerful.
11. Strong Type Checking with TypeScript
TypeScript is a programming language built on top of JavaScript, providing static type checking. By using TypeScript in React Native projects, you can achieve a safer and more error-free development process.
React Native with TypeScript:
Installation:
npx react-native init MyApp --template react-native-template-typescript
Example Usage:
type User = {
id: number;
name: string;
};
const getUser = (user: User): string => {
return User: ${user.name};
};
TypeScript helps identify errors early, especially in large projects, and makes your code more maintainable.
12. Debugging and Performance Monitoring with Reactotron
Reactotron is a powerful tool that simplifies debugging, monitoring API requests, and tracking performance in React Native projects. With Reactotron, you can monitor your application's performance and quickly resolve issues.
Reactotron Setup:
Installation:
npm install reactotron-react-native
Example Usage:
// queryClientConfig.js
import { QueryClient } from '@tanstack/react-query';
// Initialize Query Client
export const queryClient = new QueryClient();
// ReactotronConfig.js
import Reactotron from 'reactotron-react-native';
import mmkvPlugin from 'reactotron-react-native-mmkv';
import {
QueryClientManager,
reactotronReactQuery,
} from 'reactotron-react-query';
import config from './app.json';
import { queryClient } from './queryClientConfig';
import { MMKV } from 'react-native-mmkv';
// Initialize MMKV Storage
export const storage = new MMKV();
const queryClientManager = new QueryClientManager({
queryClient,
});
// Configure Reactotron
Reactotron.configure({
name: config.name,
onDisconnect: () => {
queryClientManager.unsubscribe();
},
})
.useReactNative()
.use(mmkvPlugin({ storage })) // Use the MMKV plugin to store the Reactotron state
.use(reactotronReactQuery(queryClientManager)) // Use the react-query plugin to store the Reactotron state
.connect();
With Reactotron desktop, you can view the actions performed using react-native-mmkv
and @tanstack/react-query
.
Reactotron allows you to quickly identify issues in your project by monitoring API requests and error states through the developer console.
Github Repo: Reactotron
13. App Deployment: Automated Distribution with Fastlane
You can automate the process of publishing your React Native app on the App Store and Google Play using Fastlane. This tool automates tasks such as signing, versioning, and uploading the app to the store.
App Store and Google Play Process
Create an Android App Bundle (AAB):
cd android
./gradlew bundleRelease
This command compiles your project for Android and generates the AAB file.
Create a new app in the Google Play Console and upload your AAB file.
Fill in the app details, add screenshots and icons. Wait for your app to be published.
Uploading an App to the App Store
- Archive your app via Xcode.
- Go to App Store Connect and create a new app record.
- Add the necessary information such as app icon, description, and screenshots.
- Submit your app to the App Store and wait for Apple’s review process.
Deployment with Fastlane
Installation:
sudo gem install fastlane -NV
Example Usage Fastfile:
platform :ios do
desc "App Store'a yükle"
lane :deploy_to_appstore do
increment_version_number
build_app(scheme: "YourApp")
upload_to_app_store
end
end
platform :android do
desc "Google Play'e yükle"
lane :deploy_to_playstore do
increment_version_code
gradle(task: "bundleRelease")
upload_to_play_store
end
end
Fastlane allows you to upload your app to the stores without performing manual tasks each time you version your application.
Github Repo: Fastlane
Conclusion
React Native offers a revolutionary platform for modern mobile application development with its powerful and flexible structure. In this article, we covered the most essential tools and libraries you can use when developing applications with React Native. Mastering topics like dependency management, performance optimization, data management, form validation, TypeScript integration, and debugging tools will enable you to develop your projects more efficiently and securely. Happy coding! 🚀