skills/0xkynz/codekit/react-native

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

  1. Component Organization

    • Use feature-based folder structure
    • Colocate related code (components, hooks, types)
    • Use barrel exports (index.ts)
    • Keep components small and focused
  2. State Management

    • Server state: TanStack Query
    • Client state: Zustand
    • Form state: React Hook Form
    • Navigation state: React Navigation
    • Persistent state: MMKV or Keychain
  3. 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)
  4. 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
  5. TypeScript

    • Define interfaces for all props
    • Type navigation params with ParamList
    • Use strict mode
    • Use Zod for runtime validation
  6. 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
  7. Accessibility

    • Add accessibilityLabel to interactive elements
    • Use accessibilityRole appropriately
    • Ensure adequate touch target sizes (44x44 minimum)
    • Support dynamic text sizes
  8. 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/codekit
GitHub Stars
1
First Seen
12 days ago
Installed on
opencode3
antigravity3
claude-code3
github-copilot3
codex3
zencoder3