react-native
SKILL.md
React Native CLI Development Expert
Expert in React Native development with the bare CLI workflow, TypeScript, and native module integration. Specialized in building cross-platform mobile applications that require direct native code access.
When to Use
- React Native CLI projects (bare workflow)
- Apps requiring custom native modules
- Projects with complex native integrations
- Brownfield apps (React Native in existing native apps)
- Apps needing fine-grained native control
For Expo managed workflow, use react-native-expo agent instead.
Technology Stack
Core
- React Native CLI: Bare workflow setup
- TypeScript: Strict typing and best practices
- Metro: JavaScript bundler
- Flipper: Debugging tool
UI/Styling
- NativeWind: Tailwind CSS for React Native
- React Native Reanimated: Smooth animations
- React Native Gesture Handler: Touch interactions
- React Native Vector Icons: Icon library
Navigation
- React Navigation: Stack, Tab, Drawer navigators
- React Native Screens: Native navigation primitives
Data & State
- TanStack Query: Server state management
- Zustand: Client state management
- React Hook Form + Zod: Form handling
- MMKV: Fast key-value storage
- React Native Keychain: Secure storage
Native Integration
- Turbo Modules: New Architecture native modules
- Fabric: New Architecture renderer
- CocoaPods: iOS dependency management
- Gradle: Android build system
Project Structure
/my-react-native-app
├── /android/ # Android native project
│ ├── /app/
│ │ ├── /src/
│ │ │ └── /main/
│ │ │ ├── /java/ # Java/Kotlin code
│ │ │ └── /res/ # Android resources
│ │ └── build.gradle
│ ├── build.gradle
│ ├── gradle.properties
│ └── settings.gradle
├── /ios/ # iOS native project
│ ├── /MyApp/
│ │ ├── AppDelegate.mm
│ │ ├── Info.plist
│ │ └── /Images.xcassets/
│ ├── Podfile
│ └── MyApp.xcworkspace
├── /src/
│ ├── /components/ # Reusable components
│ │ ├── /ui/ # Base UI (Button, Input, Card)
│ │ ├── /forms/ # Form components
│ │ └── /lists/ # List components
│ ├── /features/ # Feature modules
│ │ ├── /auth/
│ │ │ ├── /components/
│ │ │ ├── /hooks/
│ │ │ ├── /services/
│ │ │ └── index.ts
│ │ └── /settings/
│ ├── /hooks/ # Custom hooks
│ ├── /navigation/ # Navigation configuration
│ │ ├── RootNavigator.tsx
│ │ ├── AuthNavigator.tsx
│ │ └── MainNavigator.tsx
│ ├── /native/ # Native module bridges
│ ├── /services/ # API services
│ ├── /store/ # State management
│ ├── /types/ # TypeScript types
│ ├── /utils/ # Utilities
│ ├── /constants/ # App constants
│ └── /theme/ # Theme configuration
├── /assets/ # Images, fonts, etc.
├── App.tsx # App entry point
├── index.js # Native entry point
├── metro.config.js # Metro bundler config
├── babel.config.js
├── tsconfig.json
├── react-native.config.js # RN CLI config
└── package.json
Code Standards
Component Pattern
import { View, Text, Pressable, StyleSheet } from "react-native";
import { forwardRef } from "react";
import { cn } from "@/utils/cn";
interface ButtonProps {
variant?: "default" | "outline" | "ghost";
size?: "sm" | "md" | "lg";
onPress?: () => void;
disabled?: boolean;
className?: string;
children: React.ReactNode;
}
const Button = forwardRef<View, ButtonProps>(
({ variant = "default", size = "md", className, children, ...props }, ref) => {
return (
<Pressable
ref={ref}
className={cn(
"items-center justify-center rounded-lg",
variants[variant],
sizes[size],
props.disabled && "opacity-50",
className
)}
{...props}
>
<Text className={cn("font-medium", textVariants[variant])}>
{children}
</Text>
</Pressable>
);
}
);
Button.displayName = "Button";
export { Button };
Custom Hook Pattern
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
export function useUsers() {
return useQuery({
queryKey: ["users"],
queryFn: () => userService.getAll(),
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: userService.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
}
Form Pattern (React Hook Form + Zod)
import { View, TextInput, Text, Pressable } from "react-native";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof schema>;
export function LoginForm() {
const { control, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
const onSubmit = (data: FormData) => {
// Handle submission
};
return (
<View className="gap-4">
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value } }) => (
<View>
<TextInput
className="border border-gray-300 rounded-lg px-4 py-3"
placeholder="Email"
onBlur={onBlur}
onChangeText={onChange}
value={value}
keyboardType="email-address"
autoCapitalize="none"
/>
{errors.email && (
<Text className="text-red-500 text-sm mt-1">
{errors.email.message}
</Text>
)}
</View>
)}
/>
<Controller
control={control}
name="password"
render={({ field: { onChange, onBlur, value } }) => (
<View>
<TextInput
className="border border-gray-300 rounded-lg px-4 py-3"
placeholder="Password"
onBlur={onBlur}
onChangeText={onChange}
value={value}
secureTextEntry
/>
{errors.password && (
<Text className="text-red-500 text-sm mt-1">
{errors.password.message}
</Text>
)}
</View>
)}
/>
<Pressable
className="bg-blue-500 rounded-lg py-3 items-center"
onPress={handleSubmit(onSubmit)}
>
<Text className="text-white font-semibold">Login</Text>
</Pressable>
</View>
);
}
React Navigation Setup
// src/navigation/RootNavigator.tsx
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { AuthNavigator } from "./AuthNavigator";
import { MainNavigator } from "./MainNavigator";
import { useAuth } from "@/hooks/useAuth";
export type RootStackParamList = {
Auth: undefined;
Main: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
export function RootNavigator() {
const { isAuthenticated } = useAuth();
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isAuthenticated ? (
<Stack.Screen name="Main" component={MainNavigator} />
) : (
<Stack.Screen name="Auth" component={AuthNavigator} />
)}
</Stack.Navigator>
</NavigationContainer>
);
}
Tab Navigator
// src/navigation/MainNavigator.tsx
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import Icon from "react-native-vector-icons/Ionicons";
import { HomeScreen } from "@/features/home/screens/HomeScreen";
import { ProfileScreen } from "@/features/profile/screens/ProfileScreen";
export type MainTabParamList = {
Home: undefined;
Profile: undefined;
};
const Tab = createBottomTabNavigator<MainTabParamList>();
export function MainNavigator() {
return (
<Tab.Navigator
screenOptions={{
tabBarActiveTintColor: "#3b82f6",
tabBarInactiveTintColor: "#9ca3af",
}}
>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarIcon: ({ color, size }) => (
<Icon name="home" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarIcon: ({ color, size }) => (
<Icon name="person" size={size} color={color} />
),
}}
/>
</Tab.Navigator>
);
}
List with FlashList
import { FlashList } from "@shopify/flash-list";
import { View, Text, Pressable } from "react-native";
interface User {
id: string;
name: string;
email: string;
}
interface UsersListProps {
users: User[];
onUserPress: (user: User) => void;
}
export function UsersList({ users, onUserPress }: UsersListProps) {
const renderItem = ({ item }: { item: User }) => (
<Pressable
className="bg-white p-4 border-b border-gray-100"
onPress={() => onUserPress(item)}
>
<Text className="font-semibold text-gray-900">{item.name}</Text>
<Text className="text-gray-500 text-sm">{item.email}</Text>
</Pressable>
);
return (
<FlashList
data={users}
renderItem={renderItem}
estimatedItemSize={72}
keyExtractor={(item) => item.id}
/>
);
}
Native Module (Turbo Module)
// src/native/NativeCalculator.ts
import { TurboModule, TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
add(a: number, b: number): Promise<number>;
multiply(a: number, b: number): number;
}
export default TurboModuleRegistry.getEnforcing<Spec>("NativeCalculator");
// ios/NativeCalculator.mm
#import "NativeCalculator.h"
@implementation NativeCalculator
RCT_EXPORT_MODULE()
- (void)add:(double)a b:(double)b resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
resolve(@(a + b));
}
- (NSNumber *)multiply:(double)a b:(double)b {
return @(a * b);
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeCalculatorSpecJSI>(params);
}
@end
// android/app/src/main/java/com/myapp/NativeCalculatorModule.kt
package com.myapp
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
@ReactModule(name = NativeCalculatorModule.NAME)
class NativeCalculatorModule(reactContext: ReactApplicationContext) :
NativeCalculatorSpec(reactContext) {
override fun getName() = NAME
override fun add(a: Double, b: Double, promise: Promise) {
promise.resolve(a + b)
}
override fun multiply(a: Double, b: Double): Double {
return a * b
}
companion object {
const val NAME = "NativeCalculator"
}
}
Animation Pattern (Reanimated)
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
} from "react-native-reanimated";
import { Pressable } from "react-native";
export function AnimatedButton({ children, onPress }) {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const handlePressIn = () => {
scale.value = withSpring(0.95);
};
const handlePressOut = () => {
scale.value = withSpring(1);
};
return (
<Pressable
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onPress={onPress}
>
<Animated.View style={animatedStyle}>{children}</Animated.View>
</Pressable>
);
}
Configuration
Metro Config
// metro.config.js
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
const config = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
Babel Config
// babel.config.js
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: [
"nativewind/babel",
"react-native-reanimated/plugin",
[
"module-resolver",
{
root: ["./src"],
alias: {
"@": "./src",
},
},
],
],
};
React Native Config
// react-native.config.js
module.exports = {
project: {
ios: {},
android: {},
},
assets: ["./assets/fonts/"],
};
iOS Podfile
# ios/Podfile
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, min_ios_version_supported
prepare_react_native_project!
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
use_frameworks! :linkage => linkage.to_sym
end
target 'MyApp' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
end
end
Android Gradle
// android/app/build.gradle
android {
namespace "com.myapp"
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.myapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
}
}
dependencies {
implementation("com.facebook.react:react-android")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
Best Practices
-
Component Organization
- Use feature-based folder structure
- Colocate related code (components, hooks, types)
- Use barrel exports (index.ts)
- Keep components small and focused
-
State Management
- Server state: TanStack Query
- Client state: Zustand
- Form state: React Hook Form
- Navigation state: React Navigation
- Persistent state: MMKV or Keychain
-
Performance
- Use FlashList instead of FlatList for long lists
- Avoid inline styles and functions in render
- Use React.memo for expensive components
- Enable Hermes engine for faster JS execution
- Use the New Architecture (Fabric + Turbo Modules)
-
Native Code
- Use Turbo Modules for new native modules
- Keep native code minimal and focused
- Handle platform differences gracefully
- Test native code on both platforms
-
TypeScript
- Define interfaces for all props
- Type navigation params with ParamList
- Use strict mode
- Use Zod for runtime validation
-
Platform Handling
- Use Platform.select() for platform-specific code
- Create .ios.tsx and .android.tsx files when needed
- Test on both platforms regularly
- Handle keyboard avoidance properly
-
Accessibility
- Add accessibilityLabel to interactive elements
- Use accessibilityRole appropriately
- Ensure adequate touch target sizes (44x44 minimum)
- Support dynamic text sizes
-
Build & Release
- Use Fastlane for automated builds
- Configure proper signing for both platforms
- Set up CI/CD pipelines
- Test on real devices before release
Quick Setup Commands
# Create new React Native CLI project
npx @react-native-community/cli@latest init MyApp
cd MyApp
# Install core dependencies
npm install @tanstack/react-query zustand
npm install react-hook-form @hookform/resolvers zod
# Install navigation
npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs
npm install react-native-screens react-native-safe-area-context
# Install UI/Animation
npm install react-native-reanimated react-native-gesture-handler
npm install nativewind tailwindcss
npm install react-native-vector-icons
# Install FlashList for performant lists
npm install @shopify/flash-list
# Install storage
npm install react-native-mmkv react-native-keychain
# iOS setup
cd ios && pod install && cd ..
# Initialize NativeWind
npx tailwindcss init
# Start development
npm run ios
npm run android
Build Commands
# iOS Debug
npm run ios
# iOS Release
npm run ios -- --mode Release
# Android Debug
npm run android
# Android Release
cd android && ./gradlew assembleRelease
# Clean builds
cd ios && xcodebuild clean && cd ..
cd android && ./gradlew clean && cd ..
# Link assets (fonts, etc.)
npx react-native-asset
Debugging
# Start Metro bundler
npm start
# Start with cache reset
npm start -- --reset-cache
# Open Flipper for debugging
# Download from https://fbflipper.com/
# View logs
npx react-native log-ios
npx react-native log-android
# Open React DevTools
npx react-devtools
Weekly Installs
3
Repository
0xkynz/codekitGitHub Stars
1
First Seen
12 days ago
Security Audits
Installed on
opencode3
antigravity3
claude-code3
github-copilot3
codex3
zencoder3