RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-03-20Intermediate

Rork Practical Techniques [Part 1] — App Design, UI Building & Development Patterns

Curated practical techniques from Rork Lab premium articles. Part 1 covers app design, UI patterns, Supabase integration, and testing basics.

Rork515practical-techniques2app-design2UI10Supabase33part-1tutorial20

Setup and context — About This Article Series

This "Rork Practical Techniques" series extracts implementation patterns from Rork Lab premium courses and presents working code examples for intermediate developers.

Part 1 Topics:

  • App design best practices
  • Efficient UI building patterns
  • Basic Supabase backend integration
  • Jest unit testing

Part 2 Topics (Premium):

  • StoreKit 2 and RevenueCat monetization
  • Detox E2E testing and production quality
  • Performance optimization and offline-first design
  • CI/CD pipelines (EAS + GitHub Actions)
  • AI feature integration (Gemini API + Cloudflare Workers)

Follow this guide to build production-ready apps with Rork.


App Design Best Practices

Effective Prompt Design (Precise Instructions)

High-quality apps from Rork require clear, structured prompts.

Step 1: Define the Overall Vision

Building this app with Rork:

【App Name】
TaskFlow Pro

【Overview】
Team task management. Multiple users share and update tasks in real-time.
Easy project management.

【Platform】
iOS / Android (React Native)

【Target Users】
Small business project managers and team members

Step 2: List Main Screens

【Screen List】
1. Authentication Screen
   - Login (email + password)
   - Signup (name + email + password + company)

2. Home Screen
   - Today's task list (by priority)
   - Toggle task complete/incomplete
   - Project filter

3. Task Details Screen
   - Title, description, due date, priority
   - Assign to team member
   - Comments

4. Project Management Screen
   - Project list
   - Member management
   - Create new project

Step 3: Specify Data Models

【Database Schema (Supabase PostgreSQL)】

users table
- id (UUID, primary key)
- email (varchar, unique)
- full_name (varchar)
- company_name (varchar)
- created_at (timestamp)

projects table
- id (UUID, primary key)
- user_id (UUID, foreign key)
- name (varchar)
- description (text)
- status (enum: 'active', 'archived')
- created_at (timestamp)

tasks table
- id (UUID, primary key)
- project_id (UUID, foreign key)
- assigned_to (UUID, foreign key users)
- title (varchar)
- description (text)
- priority (enum: 'low', 'medium', 'high')
- status (enum: 'todo', 'in_progress', 'done')
- due_date (date)
- created_at (timestamp)
- updated_at (timestamp)

comments table
- id (UUID, primary key)
- task_id (UUID, foreign key)
- user_id (UUID, foreign key)
- content (text)
- created_at (timestamp)

Step 4: Specify Auth and Security

【Authentication & Security】
- Auth: Supabase Auth (email + password)
- RLS (Row Level Security): Enabled
  * users table: Read/write own records only
  * projects table: Members only
  * tasks table: Assignee and creator access
- Session: React Native AsyncStorage for JWT

Clear prompts allow Rork AI to auto-generate code meeting all requirements.

Component Splitting Patterns

Large apps need solid component architecture.

Recommended Folder Structure:

src/
├── components/
│   ├── common/
│   │   ├── Button.jsx
│   │   ├── Card.jsx
│   │   ├── Modal.jsx
│   │   └── LoadingSpinner.jsx
│   ├── auth/
│   │   ├── LoginScreen.jsx
│   │   └── SignupScreen.jsx
│   ├── tasks/
│   │   ├── TaskList.jsx
│   │   ├── TaskCard.jsx
│   │   └── TaskDetailsModal.jsx
│   └── projects/
│       ├── ProjectList.jsx
│       ├── ProjectCard.jsx
│       └── ProjectMembersModal.jsx
├── hooks/
│   ├── useAuth.js
│   ├── useSupabase.js
│   └── useTasks.js
├── context/
│   ├── AuthContext.jsx
│   └── TaskContext.jsx
├── utils/
│   ├── supabaseClient.js
│   ├── validation.js
│   └── dateFormatter.js
└── navigation/
    └── RootNavigator.jsx

Custom Hook Example (useAuth):

import { useState, useEffect, useCallback } from 'react';
import { supabase } from '../utils/supabaseClient';
 
export const useAuth = () => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    const session = supabase.auth.session();
    setUser(session?.user ?? null);
 
    const { subscription } = supabase.auth.onAuthStateChange(
      (event, session) => {
        setUser(session?.user ?? null);
        setLoading(false);
      }
    );
 
    return () => {
      subscription?.unsubscribe();
    };
  }, []);
 
  const login = useCallback(async (email, password) => {
    setLoading(true);
    setError(null);
    try {
      const { data, error } = await supabase.auth.signInWithPassword({
        email,
        password,
      });
      if (error) throw error;
      setUser(data.user);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);
 
  const logout = useCallback(async () => {
    setLoading(true);
    try {
      const { error } = await supabase.auth.signOut();
      if (error) throw error;
      setUser(null);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);
 
  return { user, loading, error, login, logout };
};

Navigation Design (Tab, Stack, Drawer)

Solid navigation structure improves UX.

Recommended Pattern: Tab + Stack Hybrid

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
 
import HomeScreen from '../screens/HomeScreen';
import ProjectsScreen from '../screens/ProjectsScreen';
import ProfileScreen from '../screens/ProfileScreen';
import TaskDetailsScreen from '../screens/TaskDetailsScreen';
 
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
 
const HomeStack = () => {
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: true,
        headerStyle: { backgroundColor: '#0066cc' },
        headerTintColor: '#fff',
        headerTitleStyle: { fontWeight: '600' },
      }}
    >
      <Stack.Screen
        name="HomeList"
        component={HomeScreen}
        options={{ title: 'Home' }}
      />
      <Stack.Screen
        name="TaskDetails"
        component={TaskDetailsScreen}
        options={{ title: 'Task Details' }}
      />
    </Stack.Navigator>
  );
};
 
const ProjectsStack = () => {
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: true,
        headerStyle: { backgroundColor: '#0066cc' },
        headerTintColor: '#fff',
      }}
    >
      <Stack.Screen
        name="ProjectsList"
        component={ProjectsScreen}
        options={{ title: 'Projects' }}
      />
      <Stack.Screen
        name="ProjectDetails"
        component={ProjectDetailsScreen}
        options={{ title: 'Project Details' }}
      />
    </Stack.Navigator>
  );
};
 
const RootNavigator = () => {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={({ route }) => ({
          headerShown: false,
          tabBarIcon: ({ focused, color, size }) => {
            let iconName;
            if (route.name === 'Home') {
              iconName = focused ? 'home' : 'home-outline';
            } else if (route.name === 'Projects') {
              iconName = focused ? 'folder' : 'folder-outline';
            } else if (route.name === 'Profile') {
              iconName = focused ? 'person' : 'person-outline';
            }
            return <Ionicons name={iconName} size={size} color={color} />;
          },
          tabBarActiveTintColor: '#0066cc',
          tabBarInactiveTintColor: '#999',
        })}
      >
        <Tab.Screen
          name="Home"
          component={HomeStack}
          options={{ title: 'Home' }}
        />
        <Tab.Screen
          name="Projects"
          component={ProjectsStack}
          options={{ title: 'Projects' }}
        />
        <Tab.Screen
          name="Profile"
          component={ProfileScreen}
          options={{ title: 'Profile' }}
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
};
 
export default RootNavigator;

UI Building in Practice

Efficient FlatList Usage (Virtualization & Memoization)

Large lists require optimization.

Core Pattern (virtualization + memoization):

import React, { useMemo, useCallback } from 'react';
import { FlatList, View, Text, StyleSheet, TouchableOpacity } from 'react-native';
 
const TaskListScreen = ({ tasks, onTaskPress, onCompleteTask }) => {
  // Sort by priority (useMemo prevents recalculation)
  const sortedTasks = useMemo(() => {
    const priorityMap = { high: 1, medium: 2, low: 3 };
    return [...tasks].sort(
      (a, b) => priorityMap[a.priority] - priorityMap[b.priority]
    );
  }, [tasks]);
 
  // Key extraction
  const keyExtractor = useCallback((item) => item.id, []);
 
  // Item component (React.memo prevents re-render)
  const TaskItem = React.memo(({ item }) => (
    <TouchableOpacity
      style={styles.taskItem}
      onPress={() => onTaskPress(item.id)}
      activeOpacity={0.7}
    >
      <View style={styles.taskContent}>
        <Text style={styles.taskTitle} numberOfLines={1}>
          {item.title}
        </Text>
        <Text style={styles.taskDescription} numberOfLines={2}>
          {item.description}
        </Text>
      </View>
      <View style={[styles.priorityBadge, styles[`priority${item.priority}`]]}>
        <Text style={styles.priorityText}>
          {item.priority === 'high' ? 'High' : item.priority === 'medium' ? 'Med' : 'Low'}
        </Text>
      </View>
    </TouchableOpacity>
  ));
 
  return (
    <FlatList
      data={sortedTasks}
      renderItem={({ item }) => <TaskItem item={item} />}
      keyExtractor={keyExtractor}
      initialNumToRender={10}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      removeClippedSubviews={true}
      contentContainerStyle={styles.container}
    />
  );
};
 
const styles = StyleSheet.create({
  container: {
    paddingVertical: 8,
  },
  taskItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  taskContent: {
    flex: 1,
  },
  taskTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
    marginBottom: 4,
  },
  taskDescription: {
    fontSize: 13,
    color: '#999',
  },
  priorityBadge: {
    paddingVertical: 4,
    paddingHorizontal: 8,
    borderRadius: 4,
    marginLeft: 12,
  },
  priorityhigh: {
    backgroundColor: '#ffcccc',
  },
  prioritymedium: {
    backgroundColor: '#fff4cc',
  },
  prioritylow: {
    backgroundColor: '#ccf0ff',
  },
  priorityText: {
    fontSize: 12,
    fontWeight: '600',
    color: '#333',
  },
});
 
export default TaskListScreen;

Accessibility Basics (VoiceOver / TalkBack)

Modern apps support everyone including people with disabilities.

Accessibility Best Practices:

import React from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  AccessibilityInfo,
} from 'react-native';
 
const AccessibleButton = ({
  title,
  onPress,
  accessibilityLabel,
  accessibilityHint,
  disabled = false,
}) => {
  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled}
      accessible={true}
      accessibilityLabel={accessibilityLabel || title}
      accessibilityHint={accessibilityHint}
      accessibilityRole="button"
      accessibilityState={{ disabled }}
      style={[styles.button, disabled && styles.buttonDisabled]}
    >
      <Text style={[styles.buttonText, disabled && styles.buttonTextDisabled]}>
        {title}
      </Text>
    </TouchableOpacity>
  );
};
 
const AccessibleTaskCard = ({ task, onPress }) => {
  return (
    <TouchableOpacity
      onPress={onPress}
      accessible={true}
      accessibilityLabel={`Task: ${task.title}`}
      accessibilityHint={`Priority: ${task.priority}, Due: ${task.dueDate}`}
      accessibilityRole="button"
      activeOpacity={0.7}
      style={styles.card}
    >
      <View>
        <Text
          style={styles.cardTitle}
          allowFontScaling={true}
          maxFontSizeMultiplier={1.5}
        >
          {task.title}
        </Text>
        <Text
          style={styles.cardDescription}
          allowFontScaling={true}
          maxFontSizeMultiplier={1.3}
        >
          {task.description}
        </Text>
      </View>
    </TouchableOpacity>
  );
};
 
const styles = StyleSheet.create({
  button: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    backgroundColor: '#0066cc',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    minHeight: 44, // iOS accessibility minimum
  },
  buttonDisabled: {
    opacity: 0.5,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
  buttonTextDisabled: {
    color: '#ccc',
  },
  card: {
    padding: 16,
    backgroundColor: '#fff',
    borderRadius: 8,
    marginVertical: 8,
    marginHorizontal: 16,
    minHeight: 56,
  },
  cardTitle: {
    fontSize: 18,
    fontWeight: '700',
    color: '#333',
    marginBottom: 8,
  },
  cardDescription: {
    fontSize: 14,
    color: '#666',
    lineHeight: 20,
  },
});
 
export default { AccessibleButton, AccessibleTaskCard };

Design Quality Tips (Color System, Typography)

Establish a Color System:

// colors.js
export const colors = {
  // Primary (CTAs, important elements)
  primary: '#0066cc',
  primaryDark: '#004fa3',
  primaryLight: '#e6f0ff',
 
  // Secondary (info, assistance)
  secondary: '#ff6b35',
  secondaryDark: '#cc5429',
  secondaryLight: '#ffe6d9',
 
  // Neutral (text, background)
  text: {
    primary: '#1a1a1a',
    secondary: '#666666',
    tertiary: '#999999',
    disabled: '#cccccc',
  },
  background: {
    primary: '#ffffff',
    secondary: '#f5f5f5',
    tertiary: '#eeeeee',
  },
  border: '#e0e0e0',
 
  // Status
  success: '#22c55e',
  warning: '#f59e0b',
  error: '#ef4444',
  info: '#3b82f6',
};
 
// typography.js
export const typography = {
  h1: {
    fontSize: 32,
    fontWeight: '700',
    lineHeight: 40,
    letterSpacing: -0.5,
  },
  h2: {
    fontSize: 24,
    fontWeight: '700',
    lineHeight: 32,
    letterSpacing: -0.3,
  },
  h3: {
    fontSize: 18,
    fontWeight: '600',
    lineHeight: 24,
  },
  body: {
    fontSize: 16,
    fontWeight: '400',
    lineHeight: 24,
  },
  bodySmall: {
    fontSize: 14,
    fontWeight: '400',
    lineHeight: 20,
  },
  caption: {
    fontSize: 12,
    fontWeight: '400',
    lineHeight: 16,
  },
  label: {
    fontSize: 12,
    fontWeight: '600',
    lineHeight: 16,
    letterSpacing: 0.5,
  },
};

Backend Integration Essentials

Supabase Auth + Database Basics (TypeScript)

Initialize Supabase Client:

import { createClient } from '@supabase/supabase-js';
import AsyncStorage from '@react-native-async-storage/async-storage';
 
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY;
 
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

Auth Service Implementation:

import { supabase } from './supabaseClient';
 
interface SignUpPayload {
  email: string;
  password: string;
  fullName: string;
  companyName: string;
}
 
interface TaskInsertPayload {
  projectId: string;
  title: string;
  description: string;
  priority: 'low' | 'medium' | 'high';
  dueDate: string;
  assignedTo?: string;
}
 
export const authService = {
  async signUp(payload: SignUpPayload) {
    const { data, error } = await supabase.auth.signUp({
      email: payload.email,
      password: payload.password,
    });
 
    if (error) throw error;
 
    const { error: profileError } = await supabase
      .from('profiles')
      .insert({
        id: data.user.id,
        full_name: payload.fullName,
        company_name: payload.companyName,
      });
 
    if (profileError) throw profileError;
 
    return data;
  },
 
  async signIn(email: string, password: string) {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });
 
    if (error) throw error;
    return data;
  },
 
  async signOut() {
    const { error } = await supabase.auth.signOut();
    if (error) throw error;
  },
 
  async getCurrentUser() {
    const { data, error } = await supabase.auth.getUser();
    if (error) throw error;
    return data.user;
  },
};
 
export const taskService = {
  async getTasks(projectId: string) {
    const { data, error } = await supabase
      .from('tasks')
      .select('*')
      .eq('project_id', projectId)
      .order('created_at', { ascending: false });
 
    if (error) throw error;
    return data;
  },
 
  async createTask(payload: TaskInsertPayload) {
    const { data, error } = await supabase
      .from('tasks')
      .insert({
        project_id: payload.projectId,
        title: payload.title,
        description: payload.description,
        priority: payload.priority,
        due_date: payload.dueDate,
        assigned_to: payload.assignedTo,
        status: 'todo',
      })
      .select()
      .single();
 
    if (error) throw error;
    return data;
  },
 
  async updateTask(
    taskId: string,
    updates: Partial<{
      title: string;
      status: 'todo' | 'in_progress' | 'done';
      priority: 'low' | 'medium' | 'high';
      due_date: string;
    }>
  ) {
    const { data, error } = await supabase
      .from('tasks')
      .update(updates)
      .eq('id', taskId)
      .select()
      .single();
 
    if (error) throw error;
    return data;
  },
 
  async deleteTask(taskId: string) {
    const { error } = await supabase
      .from('tasks')
      .delete()
      .eq('id', taskId);
 
    if (error) throw error;
  },
};

Push Notifications Basics

Firebase Cloud Messaging Setup:

import { initializeApp } from 'firebase/app';
import {
  getMessaging,
  getToken,
  onMessage,
} from 'firebase/messaging';
 
const firebaseConfig = {
  apiKey: process.env.FIREBASE_API_KEY,
  projectId: process.env.FIREBASE_PROJECT_ID,
  messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
};
 
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
 
export const notificationService = {
  async initializeNotifications() {
    try {
      const token = await getToken(messaging, {
        vapidKey: process.env.FIREBASE_VAPID_KEY,
      });
 
      if (token) {
        console.log('FCM Token:', token);
        // Save to server for push notifications
        await this.saveFcmToken(token);
      }
    } catch (error) {
      console.error('FCM error:', error);
    }
  },
 
  onMessageListener() {
    return new Promise((resolve) => {
      onMessage(messaging, (payload) => {
        console.log('Message received:', payload);
        resolve(payload);
      });
    });
  },
 
  async saveFcmToken(token: string) {
    const user = await authService.getCurrentUser();
    if (!user) return;
 
    await supabase
      .from('user_fcm_tokens')
      .insert({
        user_id: user.id,
        token,
        device_type: 'mobile',
      });
  },
};
 
// Initialize on startup
notificationService.initializeNotifications();

Deep Linking Basics

React Navigation Deep Link Configuration:

import * as Linking from 'expo-linking';
 
const prefix = Linking.createURL('/');
 
const linking = {
  prefixes: [prefix, 'https://yourapp.com', 'yourapp://'],
  config: {
    screens: {
      Home: '',
      TaskDetails: 'task/:taskId',
      ProjectDetails: 'project/:projectId',
      UserProfile: 'profile/:userId',
    },
  },
};
 
export const RootNavigator = () => {
  return (
    <NavigationContainer linking={linking} fallback={<SplashScreen />}>
      {/* Navigator definition */}
    </NavigationContainer>
  );
};
 
// Deep link handler hook
export const useDeepLinkHandler = () => {
  useEffect(() => {
    const handleUrl = ({ url }: { url: string }) => {
      const route = url.replace(/.*?:\/\//g, '');
      const routeName = route.split('/')[0];
      const params = route.split('/').slice(1);
 
      if (routeName === 'task' && params[0]) {
        navigation.navigate('TaskDetails', { taskId: params[0] });
      } else if (routeName === 'project' && params[0]) {
        navigation.navigate('ProjectDetails', { projectId: params[0] });
      }
    };
 
    const subscription = Linking.addEventListener('url', handleUrl);
 
    return () => {
      subscription.remove();
    };
  }, []);
};

Testing Fundamentals

Jest Unit Testing

Test Setup:

// jest.config.js
module.exports = {
  preset: 'jest-expo',
  testEnvironment: 'node',
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/index.ts',
  ],
  coverageThreshold: {
    global: {
      branches: 70,
      functions: 70,
      lines: 70,
      statements: 70,
    },
  },
};

Utility Function Test:

// utils/validation.test.js
import { validateEmail, validatePassword } from './validation';
 
describe('validation utilities', () => {
  describe('validateEmail', () => {
    test('should return true for valid email', () => {
      expect(validateEmail('user@example.com')).toBe(true);
    });
 
    test('should return false for invalid email', () => {
      expect(validateEmail('invalid-email')).toBe(false);
      expect(validateEmail('user@')).toBe(false);
      expect(validateEmail('')).toBe(false);
    });
  });
 
  describe('validatePassword', () => {
    test('should return true for strong password', () => {
      expect(validatePassword('Secure123!Pass')).toBe(true);
    });
 
    test('should return false for weak password', () => {
      expect(validatePassword('weak')).toBe(false);
      expect(validatePassword('12345678')).toBe(false);
      expect(validatePassword('NoNumber!')).toBe(false);
    });
  });
});

API Test with Mocking:

// services/taskService.test.js
import { taskService } from './taskService';
import { supabase } from '../utils/supabaseClient';
 
jest.mock('../utils/supabaseClient');
 
describe('taskService', () => {
  describe('getTasks', () => {
    test('should fetch tasks from Supabase', async () => {
      const mockTasks = [
        { id: '1', title: 'Task 1', status: 'todo' },
        { id: '2', title: 'Task 2', status: 'in_progress' },
      ];
 
      supabase.from.mockReturnValue({
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockResolvedValue({ data: mockTasks, error: null }),
      });
 
      const tasks = await taskService.getTasks('project-1');
 
      expect(tasks).toEqual(mockTasks);
      expect(supabase.from).toHaveBeenCalledWith('tasks');
    });
 
    test('should handle Supabase errors', async () => {
      const mockError = new Error('Network error');
 
      supabase.from.mockReturnValue({
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest
          .fn()
          .mockResolvedValue({ data: null, error: mockError }),
      });
 
      await expect(taskService.getTasks('project-1')).rejects.toThrow(
        'Network error'
      );
    });
  });
});

Component Testing with React Native Testing Library

Component Test Example:

// components/tasks/TaskCard.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import TaskCard from './TaskCard';
 
describe('TaskCard', () => {
  const mockTask = {
    id: '1',
    title: 'Test Task',
    description: 'Task description',
    priority: 'high',
    dueDate: '2026-03-25',
  };
 
  test('should render task title and description', () => {
    render(<TaskCard task={mockTask} onPress={jest.fn()} />);
 
    expect(screen.getByText('Test Task')).toBeTruthy();
    expect(screen.getByText('Task description')).toBeTruthy();
  });
 
  test('should call onPress when card is tapped', () => {
    const mockOnPress = jest.fn();
    render(<TaskCard task={mockTask} onPress={mockOnPress} />);
 
    const taskButton = screen.getByRole('button');
    fireEvent.press(taskButton);
 
    expect(mockOnPress).toHaveBeenCalledWith(mockTask.id);
  });
 
  test('should display high priority badge', () => {
    render(<TaskCard task={mockTask} onPress={jest.fn()} />);
 
    expect(screen.getByText('High')).toBeTruthy();
  });
});

Next Steps — Part 2 (Premium Topics)

Part 1 covered design and implementation fundamentals. Part 2 covers:

  1. App Monetization — StoreKit 2, RevenueCat, AdMob integration
  2. Production Quality — Detox E2E testing, Sentry crash analytics
  3. CI/CD Pipeline — EAS Build/Update + GitHub Actions
  4. Performance Optimization — Memory management, offline-first design
  5. AI Integration — Gemini API streaming, Cloudflare Workers
  6. App Store Strategy — ASO, marketing

Complete Part 2 and you'll build apps targeting millions of downloads.


Article Info

  • Published: March 20, 2026
  • Compatible With: Rork v3.2.0+, React Native 0.73.0+
  • Related Articles
    • "Rork Complete Guide 2026" (beginner)
    • "Rork Practical Techniques [Part 2]" (premium, advanced)

Questions? Visit Rork Lab Discord community.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-07-08
Building a Segmented Control With a Sliding Indicator in Reanimated
The stock segmented control looks out of place on Android. Here is a custom Reanimated component that measures each segment and slides the indicator, with complete code plus UI-thread rendering, accessibility, and RTL handling from real shipping notes.
Dev Tools2026-07-03
When Your App Store Connect API Pipeline Quietly Drops Days — Field Notes on JWT Expiry, Report Lag, and Reconciliation
Automated App Store Connect API pipelines rarely stop — they leak. This piece breaks down the three silent failure modes behind 401, 404, and 429, and shows how a fetch ledger, a 72-hour backfill window, and a weekly reconciliation query keep daily sales and review data complete.
Dev Tools2026-06-23
DAU Went Up but Retention Didn't — Rebuilding Gamification That Actually Sticks in Rork Apps
Points, badges, and leaderboards lift DAU, but retention is a different story. Field notes on a server-authoritative point ledger, streaks that forgive, and leaderboards that don't crush newcomers — with working code for Rork apps.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →