30 Pertanyaan Wawancara React Native Teratas: Panduan Lengkap 2026

30 pertanyaan wawancara React Native yang paling sering diajukan. Jawaban terperinci dengan contoh kode untuk meraih posisi sebagai pengembang mobile.

Ilustrasi pertanyaan wawancara React Native dengan komponen mobile yang saling terhubung

Wawancara teknis React Native menilai keterampilan pengembangan mobile lintas platform, kekhususan iOS/Android, serta pola performa. Panduan ini membahas 30 pertanyaan yang paling sering diajukan, lengkap dengan jawaban terperinci dan contoh kode untuk persiapan yang efektif.

Saran Persiapan

Pertanyaan-pertanyaan ini mencakup hal mendasar hingga konsep lanjutan. Penguasaan arsitektur React Native dan pemahaman perbedaannya dengan React web menjadi kunci utama keberhasilan dalam wawancara.

Dasar-Dasar React Native

1. Apa perbedaan antara React dan React Native?

React merupakan pustaka untuk membangun antarmuka web, sedangkan React Native memungkinkan pengembangan aplikasi mobile native untuk iOS maupun Android.

Perbedaan mendasarnya terletak pada proses rendering: React menggunakan Virtual DOM yang diterjemahkan menjadi elemen HTML, sementara React Native memanfaatkan jembatan (bridge) yang berkomunikasi dengan komponen native pada masing-masing platform.

jsx
// React (Web) - uses HTML elements
function WebComponent() {
  return (
    <div className="container">
      <span>Web text</span>
      <button onClick={handleClick}>Click</button>
    </div>
  )
}

// React Native - uses native components
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'

function NativeComponent() {
  return (
    <View style={styles.container}>
      <Text>Native text</Text>
      <TouchableOpacity onPress={handlePress}>
        <Text>Press</Text>
      </TouchableOpacity>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16
  }
})

Komponen React Native diterjemahkan menjadi UIView pada iOS dan android.view pada Android, sehingga menghasilkan performa setara aplikasi native.

2. Bagaimana cara kerja arsitektur React Native?

React Native menggunakan arsitektur tiga lapis: JavaScript, Bridge (atau JSI pada arsitektur baru), dan Native.

Kode JavaScript dijalankan pada mesin JS (Hermes atau JavaScriptCore). Komunikasi dengan kode native melalui serialisasi JSON pada arsitektur lama, atau melalui JSI (JavaScript Interface) pada arsitektur baru.

jsx
// Old architecture: asynchronous communication via Bridge
// The Bridge serializes messages as JSON between JS and Native

// New architecture (Fabric + TurboModules)
// JSI enables synchronous direct calls to native modules

// Example TurboModule usage
import { TurboModuleRegistry } from 'react-native'

// Synchronous access to native module
const DeviceInfo = TurboModuleRegistry.get('DeviceInfo')
const deviceName = DeviceInfo.getDeviceName() // Synchronous call

// With Fabric, rendering is smoother
// Components can be created synchronously
// Reducing jank during animations

Arsitektur baru meningkatkan performa secara signifikan dengan menghilangkan serialisasi JSON dan memungkinkan pemanggilan sinkron.

3. Apa itu Metro bundler?

Metro adalah bundler JavaScript yang digunakan oleh React Native. Bundler ini mengubah kode sumber menjadi bundle yang dioptimalkan untuk eksekusi di perangkat mobile.

Metro menangani resolusi modul, transformasi kode (melalui Babel), serta hot reloading selama pengembangan.

metro.config.jsjavascript
const { getDefaultConfig } = require('expo/metro-config')

const config = getDefaultConfig(__dirname)

// Custom configuration
config.resolver.assetExts.push('db') // Add extensions
config.resolver.sourceExts.push('cjs') // CommonJS support

// Transformer configuration
config.transformer.babelTransformerPath = require.resolve(
  'react-native-svg-transformer'
)

// Production optimizations
config.transformer.minifierConfig = {
  keep_classnames: true,
  keep_fnames: true,
  mangle: {
    keep_classnames: true,
    keep_fnames: true
  }
}

module.exports = config

Metro mendukung Fast Refresh, sehingga perubahan dapat diterapkan secara langsung tanpa kehilangan state aplikasi.

4. Jelaskan StyleSheet.create dan keuntungannya

StyleSheet.create mengoptimalkan style dengan memvalidasinya dan mengonversinya menjadi referensi numerik, sehingga mengurangi beban pada bridge.

jsx
// ❌ Inline styles - recreated on every render
function BadExample() {
  return (
    <View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
      <Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
    </View>
  )
}

// ✅ StyleSheet.create - optimized and validated
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#fff'
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold'
  },
  // Style composition
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8
  }
})

function GoodExample() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Title</Text>
      {/* Style combination */}
      <View style={[styles.row, { marginTop: 10 }]}>
        <Text>Content</Text>
      </View>
    </View>
  )
}

// StyleSheet.absoluteFillObject for absolute positioning
const overlayStyles = StyleSheet.create({
  overlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0,0,0,0.5)'
  }
})

5. Apa perbedaan antara Flexbox web dan React Native?

React Native menggunakan Flexbox tetapi dengan nilai default yang berbeda dari versi web, disesuaikan untuk antarmuka mobile vertikal.

jsx
// Key differences from web
const styles = StyleSheet.create({
  container: {
    // flexDirection: 'column' by default (vs 'row' on web)
    // alignItems: 'stretch' by default
    flex: 1
  },

  // React Native Flexbox
  row: {
    flexDirection: 'row',    // Horizontal
    justifyContent: 'space-between', // Main axis
    alignItems: 'center',    // Cross axis
    flexWrap: 'wrap',        // Line wrapping
    gap: 8                   // Supported since RN 0.71
  },

  // Flex grow/shrink
  flexItem: {
    flex: 1,        // Equivalent to flex: 1 1 0
    flexGrow: 1,    // Grow to fill
    flexShrink: 0,  // Don't shrink
    flexBasis: 100  // Base size
  },

  // Absolute positioning
  absolute: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0
  }
})

// Practical example: card with image and content
function Card() {
  return (
    <View style={cardStyles.container}>
      <Image source={{ uri: imageUrl }} style={cardStyles.image} />
      <View style={cardStyles.content}>
        <Text style={cardStyles.title}>Title</Text>
        <Text style={cardStyles.description}>Description</Text>
      </View>
    </View>
  )
}

const cardStyles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    backgroundColor: '#fff',
    borderRadius: 8,
    overflow: 'hidden'
  },
  image: {
    width: 100,
    height: 100
  },
  content: {
    flex: 1, // Takes remaining space
    padding: 12,
    justifyContent: 'center'
  },
  title: {
    fontSize: 16,
    fontWeight: '600'
  },
  description: {
    fontSize: 14,
    color: '#666'
  }
})

6. Bagaimana cara mengimplementasikan navigasi dengan React Navigation?

React Navigation merupakan solusi standar untuk navigasi pada React Native. Pustaka ini menyediakan berbagai jenis navigator yang sesuai dengan pola mobile.

jsx
// Installing dependencies
// npm install @react-navigation/native @react-navigation/native-stack
// npm install react-native-screens react-native-safe-area-context

import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'

// TypeScript typing for navigation params
type RootStackParamList = {
  Home: undefined
  Profile: { userId: string }
  Settings: { section?: string }
}

const Stack = createNativeStackNavigator<RootStackParamList>()
const Tab = createBottomTabNavigator()

// Tab navigation
function TabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          // Dynamic icon based on tab
          const iconName = route.name === 'Home' ? 'home' : 'settings'
          return <Icon name={iconName} size={size} color={color} />
        },
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: 'gray'
      })}
    >
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  )
}

// Stack navigation
function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{
          headerStyle: { backgroundColor: '#007AFF' },
          headerTintColor: '#fff',
          animation: 'slide_from_right' // Native animation
        }}
      >
        <Stack.Screen
          name="Home"
          component={TabNavigator}
          options={{ headerShown: false }}
        />
        <Stack.Screen
          name="Profile"
          component={ProfileScreen}
          options={({ route }) => ({
            title: `Profile ${route.params.userId}`
          })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  )
}

7. Bagaimana cara menangani daftar performa tinggi dengan FlatList?

FlatList dioptimalkan untuk daftar panjang melalui virtualisasi otomatis, hanya merender elemen yang terlihat di layar.

jsx
import { FlatList, RefreshControl } from 'react-native'

function ProductList() {
  const [products, setProducts] = useState([])
  const [refreshing, setRefreshing] = useState(false)
  const [loading, setLoading] = useState(false)

  // Initial loading
  const fetchProducts = async (page = 1) => {
    const response = await api.getProducts(page)
    return response.data
  }

  // Pull-to-refresh
  const onRefresh = useCallback(async () => {
    setRefreshing(true)
    const data = await fetchProducts(1)
    setProducts(data)
    setRefreshing(false)
  }, [])

  // Infinite pagination
  const loadMore = useCallback(async () => {
    if (loading) return
    setLoading(true)
    const nextPage = Math.ceil(products.length / 20) + 1
    const data = await fetchProducts(nextPage)
    setProducts(prev => [...prev, ...data])
    setLoading(false)
  }, [products.length, loading])

  // Item rendering
  const renderItem = useCallback(({ item }) => (
    <ProductCard product={item} />
  ), [])

  // Key extraction
  const keyExtractor = useCallback((item) => item.id.toString(), [])

  // Item separator
  const ItemSeparator = useCallback(() => (
    <View style={{ height: 12 }} />
  ), [])

  return (
    <FlatList
      data={products}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      ItemSeparatorComponent={ItemSeparator}
      // Performance optimizations
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      windowSize={5}
      // Pull-to-refresh
      refreshControl={
        <RefreshControl
          refreshing={refreshing}
          onRefresh={onRefresh}
          tintColor="#007AFF"
        />
      }
      // Infinite pagination
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
      ListFooterComponent={loading ? <ActivityIndicator /> : null}
      // Empty list
      ListEmptyComponent={<EmptyState message="No products" />}
    />
  )
}
Optimasi FlatList

Selalu memoisasi renderItem menggunakan useCallback dan ekstrak komponen yang berat. Hindari fungsi inline pada renderItem yang dapat memicu re-render yang tidak perlu.

8. Apa perbedaan antara TouchableOpacity, Pressable, dan TouchableHighlight?

Komponen-komponen ini menangani interaksi sentuh dengan umpan balik visual yang berbeda.

jsx
import {
  TouchableOpacity,
  TouchableHighlight,
  Pressable,
  StyleSheet
} from 'react-native'

function InteractionExamples() {
  return (
    <View style={styles.container}>
      {/* TouchableOpacity: reduces opacity on touch */}
      <TouchableOpacity
        activeOpacity={0.7}
        onPress={() => console.log('Pressed')}
        style={styles.button}
      >
        <Text>TouchableOpacity</Text>
      </TouchableOpacity>

      {/* TouchableHighlight: adds background color */}
      <TouchableHighlight
        underlayColor="#ddd"
        onPress={() => console.log('Pressed')}
        style={styles.button}
      >
        <Text>TouchableHighlight</Text>
      </TouchableHighlight>

      {/* Pressable: modern API with more control */}
      <Pressable
        onPress={() => console.log('Pressed')}
        onLongPress={() => console.log('Long press')}
        delayLongPress={500}
        style={({ pressed }) => [
          styles.button,
          pressed && styles.buttonPressed
        ]}
      >
        {({ pressed }) => (
          <Text style={pressed && styles.textPressed}>
            {pressed ? 'Pressed!' : 'Pressable'}
          </Text>
        )}
      </Pressable>

      {/* Pressable with hitSlop to enlarge touch area */}
      <Pressable
        hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
        onPress={() => console.log('Pressed')}
        style={styles.smallButton}
      >
        <Text>Small button</Text>
      </Pressable>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    gap: 16,
    padding: 20
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center'
  },
  buttonPressed: {
    backgroundColor: '#0056b3',
    transform: [{ scale: 0.98 }]
  },
  textPressed: {
    color: '#fff'
  },
  smallButton: {
    padding: 8,
    backgroundColor: '#eee'
  }
})

Pressable direkomendasikan untuk proyek baru karena menawarkan kontrol lebih luas dan API yang lebih konsisten.

9. Bagaimana cara membuat animasi yang halus?

React Native menyediakan beberapa API animasi: Animated (bawaan) dan Reanimated (lebih performan).

jsx
import { Animated, Easing } from 'react-native'
import Reanimated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming
} from 'react-native-reanimated'

// Animation with Animated (native API)
function FadeInView({ children }) {
  const fadeAnim = useRef(new Animated.Value(0)).current

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 500,
      easing: Easing.ease,
      useNativeDriver: true // Performant on UI thread
    }).start()
  }, [])

  return (
    <Animated.View style={{ opacity: fadeAnim }}>
      {children}
    </Animated.View>
  )
}

// Animation with Reanimated (recommended for complex animations)
function BouncyButton() {
  const scale = useSharedValue(1)

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }]
  }))

  const handlePressIn = () => {
    scale.value = withSpring(0.95, {
      damping: 10,
      stiffness: 400
    })
  }

  const handlePressOut = () => {
    scale.value = withSpring(1, {
      damping: 10,
      stiffness: 400
    })
  }

  return (
    <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
      <Reanimated.View style={[styles.button, animatedStyle]}>
        <Text style={styles.buttonText}>Press</Text>
      </Reanimated.View>
    </Pressable>
  )
}

// List animation with LayoutAnimation
import { LayoutAnimation, UIManager, Platform } from 'react-native'

// Enable on Android
if (Platform.OS === 'android') {
  UIManager.setLayoutAnimationEnabledExperimental?.(true)
}

function AnimatedList() {
  const [items, setItems] = useState([])

  const addItem = () => {
    // Configure animation before state change
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring)
    setItems(prev => [...prev, { id: Date.now() }])
  }

  const removeItem = (id) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
    setItems(prev => prev.filter(item => item.id !== id))
  }

  return (
    <View>
      <Button title="Add" onPress={addItem} />
      {items.map(item => (
        <TouchableOpacity key={item.id} onPress={() => removeItem(item.id)}>
          <View style={styles.item}>
            <Text>Item {item.id}</Text>
          </View>
        </TouchableOpacity>
      ))}
    </View>
  )
}

Siap menguasai wawancara React Native Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Manajemen State dan Data

10. Bagaimana cara mengelola state global pada React Native?

Solusi yang sama dengan React web dapat diterapkan: Context API, Redux, Zustand, atau MobX.

jsx
// Lightweight solution with Zustand
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'

// Store with persistence
const useAuthStore = create(
  persist(
    (set, get) => ({
      user: null,
      token: null,
      isAuthenticated: false,

      login: async (email, password) => {
        const response = await api.login(email, password)
        set({
          user: response.user,
          token: response.token,
          isAuthenticated: true
        })
      },

      logout: () => {
        set({ user: null, token: null, isAuthenticated: false })
      },

      updateProfile: (updates) => {
        set(state => ({
          user: { ...state.user, ...updates }
        }))
      }
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage)
    }
  )
)

// Usage in a component
function ProfileScreen() {
  const { user, logout, updateProfile } = useAuthStore()

  if (!user) return <LoginPrompt />

  return (
    <View style={styles.container}>
      <Text style={styles.name}>{user.name}</Text>
      <Text style={styles.email}>{user.email}</Text>

      <TouchableOpacity onPress={logout} style={styles.logoutButton}>
        <Text>Logout</Text>
      </TouchableOpacity>
    </View>
  )
}

// Cart store
const useCartStore = create((set, get) => ({
  items: [],

  addItem: (product) => set(state => {
    const existing = state.items.find(i => i.id === product.id)
    if (existing) {
      return {
        items: state.items.map(i =>
          i.id === product.id
            ? { ...i, quantity: i.quantity + 1 }
            : i
        )
      }
    }
    return { items: [...state.items, { ...product, quantity: 1 }] }
  }),

  removeItem: (id) => set(state => ({
    items: state.items.filter(i => i.id !== id)
  })),

  getTotal: () => {
    return get().items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0
    )
  },

  clearCart: () => set({ items: [] })
}))

11. Bagaimana cara melakukan panggilan API dengan manajemen cache?

React Query (TanStack Query) merupakan solusi yang direkomendasikan untuk manajemen data server.

jsx
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'

// Client configuration
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      cacheTime: 30 * 60 * 1000, // 30 minutes
      retry: 2,
      refetchOnWindowFocus: false // Mobile doesn't have "window focus"
    }
  }
})

// Provider in App.tsx
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <NavigationContainer>
        <AppNavigator />
      </NavigationContainer>
    </QueryClientProvider>
  )
}

// Custom hook for products
function useProducts(categoryId) {
  return useQuery({
    queryKey: ['products', categoryId],
    queryFn: async () => {
      const response = await fetch(`/api/products?category=${categoryId}`)
      if (!response.ok) throw new Error('Network error')
      return response.json()
    },
    enabled: !!categoryId // Don't execute if no categoryId
  })
}

// Mutation with cache invalidation
function useAddToCart() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (product) => {
      const response = await fetch('/api/cart', {
        method: 'POST',
        body: JSON.stringify(product)
      })
      return response.json()
    },
    onSuccess: () => {
      // Invalidate cart cache to force refetch
      queryClient.invalidateQueries({ queryKey: ['cart'] })
    },
    onError: (error) => {
      Alert.alert('Error', error.message)
    }
  })
}

// Usage in a component
function ProductListScreen({ categoryId }) {
  const { data: products, isLoading, error, refetch } = useProducts(categoryId)
  const addToCart = useAddToCart()

  if (isLoading) return <LoadingSpinner />
  if (error) return <ErrorView error={error} onRetry={refetch} />

  return (
    <FlatList
      data={products}
      renderItem={({ item }) => (
        <ProductCard
          product={item}
          onAddToCart={() => addToCart.mutate(item)}
          isAddingToCart={addToCart.isPending}
        />
      )}
      refreshing={isLoading}
      onRefresh={refetch}
    />
  )
}

12. Bagaimana cara menyimpan data secara lokal?

Terdapat beberapa opsi tergantung jenis data: AsyncStorage untuk data sederhana, MMKV untuk performa tinggi, dan SQLite untuk data terstruktur.

jsx
// AsyncStorage - simple but slow for large volumes
import AsyncStorage from '@react-native-async-storage/async-storage'

const storage = {
  async get(key) {
    const value = await AsyncStorage.getItem(key)
    return value ? JSON.parse(value) : null
  },

  async set(key, value) {
    await AsyncStorage.setItem(key, JSON.stringify(value))
  },

  async remove(key) {
    await AsyncStorage.removeItem(key)
  },

  async clear() {
    await AsyncStorage.clear()
  }
}

// MMKV - very performant (recommended)
import { MMKV } from 'react-native-mmkv'

const mmkv = new MMKV()

const fastStorage = {
  get(key) {
    const value = mmkv.getString(key)
    return value ? JSON.parse(value) : null
  },

  set(key, value) {
    mmkv.set(key, JSON.stringify(value))
  },

  remove(key) {
    mmkv.delete(key)
  },

  // Primitive type support
  getNumber(key) {
    return mmkv.getNumber(key)
  },

  setNumber(key, value) {
    mmkv.set(key, value)
  },

  getBoolean(key) {
    return mmkv.getBoolean(key)
  }
}

// Hook to use MMKV with React
function useMMKVStorage(key, defaultValue) {
  const [value, setValue] = useState(() => {
    const stored = fastStorage.get(key)
    return stored ?? defaultValue
  })

  const setStoredValue = useCallback((newValue) => {
    setValue(newValue)
    fastStorage.set(key, newValue)
  }, [key])

  return [value, setStoredValue]
}

// Usage
function SettingsScreen() {
  const [theme, setTheme] = useMMKVStorage('theme', 'light')
  const [notifications, setNotifications] = useMMKVStorage('notifications', true)

  return (
    <View>
      <Switch
        value={theme === 'dark'}
        onValueChange={(v) => setTheme(v ? 'dark' : 'light')}
      />
      <Switch
        value={notifications}
        onValueChange={setNotifications}
      />
    </View>
  )
}
Data Sensitif

Untuk token dan data sensitif lainnya, sebaiknya gunakan expo-secure-store atau react-native-keychain yang mengenkripsi data melalui Keychain (iOS) maupun Keystore (Android).

Performa dan Optimasi

13. Bagaimana cara mengoptimalkan performa aplikasi React Native?

Optimasi mencakup beberapa aspek: rendering, memori, dan interaksi.

1. Avoid unnecessary re-renders with React.memojsx
const ProductCard = React.memo(function ProductCard({ product, onPress }) {
  return (
    <TouchableOpacity onPress={() => onPress(product.id)}>
      <View style={styles.card}>
        <Image source={{ uri: product.image }} style={styles.image} />
        <Text style={styles.title}>{product.name}</Text>
        <Text style={styles.price}>{product.price}$</Text>
      </View>
    </TouchableOpacity>
  )
}, (prevProps, nextProps) => {
  // Custom comparison
  return prevProps.product.id === nextProps.product.id
})

// 2. Memoize callbacks
function ProductList({ products }) {
  // ❌ New function on every render
  // onPress={(id) => handlePress(id)}

  // ✅ Stable function
  const handlePress = useCallback((id) => {
    navigation.navigate('Product', { id })
  }, [navigation])

  return (
    <FlatList
      data={products}
      renderItem={({ item }) => (
        <ProductCard product={item} onPress={handlePress} />
      )}
    />
  )
}

// 3. Optimize images
import FastImage from 'react-native-fast-image'

function OptimizedImage({ uri }) {
  return (
    <FastImage
      source={{
        uri,
        priority: FastImage.priority.normal,
        cache: FastImage.cacheControl.immutable
      }}
      style={styles.image}
      resizeMode={FastImage.resizeMode.cover}
    />
  )
}

// 4. Use InteractionManager for heavy tasks
import { InteractionManager } from 'react-native'

function HeavyScreen() {
  const [data, setData] = useState(null)

  useEffect(() => {
    // Wait for animations to complete
    const task = InteractionManager.runAfterInteractions(() => {
      const result = performHeavyComputation()
      setData(result)
    })

    return () => task.cancel()
  }, [])

  return data ? <DataView data={data} /> : <LoadingView />
}

// 5. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))

function Navigator() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Heavy"
        component={HeavyScreen}
        options={{ lazy: true }}
      />
    </Stack.Navigator>
  )
}

14. Bagaimana cara melakukan debug masalah performa?

React Native menyediakan beberapa alat untuk mengidentifikasi penyebab kemacetan.

1. Flipper - main debugging tooljsx
// Configuration in android/app/build.gradle and ios/Podfile
// See: https://fbflipper.com/

// 2. Console.time to measure operations
function fetchData() {
  console.time('fetchData')
  const data = await api.getData()
  console.timeEnd('fetchData') // fetchData: 234ms
  return data
}

// 3. Performance monitor (shake device → Show Perf Monitor)
// Shows JS and UI FPS

// 4. Hermes profiler for CPU
// Enable in metro.config.js
module.exports = {
  transformer: {
    hermesParser: true
  }
}

// 5. Identify re-renders with why-did-you-render
// Installation: npm install @welldone-software/why-did-you-render
import React from 'react'

if (__DEV__) {
  const whyDidYouRender = require('@welldone-software/why-did-you-render')
  whyDidYouRender(React, {
    trackAllPureComponents: true
  })
}

// Mark a component to monitor
ProductCard.whyDidYouRender = true

// 6. Measure mount time
function useComponentTiming(componentName) {
  const mountTime = useRef(Date.now())

  useEffect(() => {
    const duration = Date.now() - mountTime.current
    console.log(`${componentName} mounted in ${duration}ms`)

    return () => {
      console.log(`${componentName} unmounted`)
    }
  }, [componentName])
}

// Usage
function MyComponent() {
  useComponentTiming('MyComponent')
  // ...
}

15. Bagaimana cara menangani mode offline?

Penanganan mode offline membutuhkan strategi caching dan sinkronisasi yang baik.

jsx
import NetInfo from '@react-native-community/netinfo'

// Hook to monitor connectivity
function useNetworkStatus() {
  const [isConnected, setIsConnected] = useState(true)
  const [connectionType, setConnectionType] = useState(null)

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      setIsConnected(state.isConnected)
      setConnectionType(state.type)
    })

    return () => unsubscribe()
  }, [])

  return { isConnected, connectionType }
}

// Service with offline queue
class OfflineQueue {
  constructor() {
    this.queue = []
    this.isProcessing = false
  }

  async add(action) {
    this.queue.push({
      id: Date.now(),
      action,
      timestamp: new Date().toISOString()
    })
    await this.persist()
  }

  async persist() {
    await AsyncStorage.setItem('offline_queue', JSON.stringify(this.queue))
  }

  async load() {
    const data = await AsyncStorage.getItem('offline_queue')
    this.queue = data ? JSON.parse(data) : []
  }

  async process() {
    if (this.isProcessing || this.queue.length === 0) return

    this.isProcessing = true
    const { isConnected } = await NetInfo.fetch()

    if (!isConnected) {
      this.isProcessing = false
      return
    }

    while (this.queue.length > 0) {
      const item = this.queue[0]
      try {
        await this.executeAction(item.action)
        this.queue.shift()
        await this.persist()
      } catch (error) {
        console.error('Failed to process action:', error)
        break
      }
    }

    this.isProcessing = false
  }

  async executeAction(action) {
    switch (action.type) {
      case 'CREATE_ORDER':
        return api.createOrder(action.payload)
      case 'UPDATE_PROFILE':
        return api.updateProfile(action.payload)
      default:
        throw new Error(`Unknown action: ${action.type}`)
    }
  }
}

const offlineQueue = new OfflineQueue()

// Network status banner component
function NetworkBanner() {
  const { isConnected } = useNetworkStatus()

  if (isConnected) return null

  return (
    <View style={styles.banner}>
      <Text style={styles.bannerText}>
        Offline mode - Changes will be synchronized
      </Text>
    </View>
  )
}

Kekhususan Mobile

16. Bagaimana cara menangani permission pada iOS dan Android?

Permission ditangani secara berbeda pada masing-masing platform. Pustaka seperti react-native-permissions menyatukan API tersebut.

jsx
import { Platform, Alert, Linking } from 'react-native'
import {
  check,
  request,
  PERMISSIONS,
  RESULTS,
  openSettings
} from 'react-native-permissions'

// Permission configuration per platform
const PERMISSION_TYPES = {
  camera: Platform.select({
    ios: PERMISSIONS.IOS.CAMERA,
    android: PERMISSIONS.ANDROID.CAMERA
  }),
  photos: Platform.select({
    ios: PERMISSIONS.IOS.PHOTO_LIBRARY,
    android: PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
  }),
  location: Platform.select({
    ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
    android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
  })
}

// Hook to manage permissions
function usePermission(type) {
  const [status, setStatus] = useState(RESULTS.UNAVAILABLE)

  const permission = PERMISSION_TYPES[type]

  useEffect(() => {
    check(permission).then(setStatus)
  }, [permission])

  const requestPermission = useCallback(async () => {
    const result = await request(permission)
    setStatus(result)

    if (result === RESULTS.BLOCKED) {
      Alert.alert(
        'Permission required',
        `This feature requires ${type} access. Do you want to open settings?`,
        [
          { text: 'No', style: 'cancel' },
          { text: 'Open', onPress: openSettings }
        ]
      )
    }

    return result
  }, [permission, type])

  return {
    status,
    isGranted: status === RESULTS.GRANTED,
    isDenied: status === RESULTS.DENIED,
    isBlocked: status === RESULTS.BLOCKED,
    requestPermission
  }
}

// Usage
function CameraButton() {
  const { isGranted, requestPermission } = usePermission('camera')

  const handlePress = async () => {
    if (!isGranted) {
      const result = await requestPermission()
      if (result !== RESULTS.GRANTED) return
    }
    // Open camera
    navigation.navigate('Camera')
  }

  return (
    <TouchableOpacity onPress={handlePress}>
      <Text>Take a photo</Text>
    </TouchableOpacity>
  )
}

17. Bagaimana cara mengimplementasikan push notification?

Push notification membutuhkan konfigurasi native serta backend untuk pengirimannya.

jsx
import messaging from '@react-native-firebase/messaging'
import notifee, { AndroidImportance } from '@notifee/react-native'

// Notification service
class NotificationService {
  async initialize() {
    // Request permission (iOS)
    const authStatus = await messaging().requestPermission()
    const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED

    if (enabled) {
      // Get FCM token
      const token = await messaging().getToken()
      await this.registerToken(token)

      // Listen for token changes
      messaging().onTokenRefresh(this.registerToken)

      // Create Android channel
      await notifee.createChannel({
        id: 'default',
        name: 'Notifications',
        importance: AndroidImportance.HIGH
      })
    }

    return enabled
  }

  async registerToken(token) {
    // Send token to backend
    await api.registerPushToken(token)
  }

  // Setup handlers
  setupHandlers() {
    // Notification received in foreground
    messaging().onMessage(async (remoteMessage) => {
      await this.displayNotification(remoteMessage)
    })

    // Notification tapped (app in background)
    messaging().onNotificationOpenedApp((remoteMessage) => {
      this.handleNotificationPress(remoteMessage)
    })

    // App opened from notification (app closed)
    messaging()
      .getInitialNotification()
      .then((remoteMessage) => {
        if (remoteMessage) {
          this.handleNotificationPress(remoteMessage)
        }
      })
  }

  async displayNotification(remoteMessage) {
    const { title, body } = remoteMessage.notification

    await notifee.displayNotification({
      title,
      body,
      android: {
        channelId: 'default',
        pressAction: { id: 'default' }
      },
      data: remoteMessage.data
    })
  }

  handleNotificationPress(remoteMessage) {
    const { type, id } = remoteMessage.data

    switch (type) {
      case 'order':
        navigation.navigate('OrderDetail', { orderId: id })
        break
      case 'message':
        navigation.navigate('Chat', { conversationId: id })
        break
    }
  }
}

// Usage in App.tsx
function App() {
  useEffect(() => {
    const notificationService = new NotificationService()
    notificationService.initialize()
    notificationService.setupHandlers()
  }, [])

  return <AppNavigator />
}

Deep link memungkinkan aplikasi terbuka langsung pada layar tertentu dari URL eksternal.

jsx
import { Linking } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'

// Deep link configuration
const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],

  config: {
    screens: {
      Home: 'home',
      Product: {
        path: 'product/:id',
        parse: {
          id: (id) => parseInt(id, 10)
        }
      },
      Profile: 'profile/:userId?',
      Settings: {
        path: 'settings',
        screens: {
          Notifications: 'notifications',
          Privacy: 'privacy'
        }
      }
    }
  },

  // Custom function to get initial URL
  async getInitialURL() {
    // Check if app was opened via deep link
    const url = await Linking.getInitialURL()
    if (url) return url

    // Check notifications
    const message = await messaging().getInitialNotification()
    if (message?.data?.link) return message.data.link

    return null
  },

  // Subscribe to incoming links
  subscribe(listener) {
    // Standard deep links
    const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
      listener(url)
    })

    // Links from notifications
    const unsubscribeNotification = messaging().onNotificationOpenedApp(
      (message) => {
        const link = message.data?.link
        if (link) listener(link)
      }
    )

    return () => {
      linkingSubscription.remove()
      unsubscribeNotification()
    }
  }
}

// Usage
function App() {
  return (
    <NavigationContainer linking={linking} fallback={<LoadingScreen />}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Product" component={ProductScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}

// Testing deep links
// myapp://product/123
// https://myapp.com/profile/user456

Siap menguasai wawancara React Native Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Kode dan Modul Native

19. Kapan dan bagaimana menulis kode native?

Kode native dibutuhkan untuk mengakses fitur yang tidak tersedia di JavaScript atau untuk mengoptimalkan performa kritis.

jsx
// iOS - Native module in Swift
// ios/MyModule.swift
import Foundation

@objc(MyModule)
class MyModule: NSObject {

  @objc
  func getDeviceInfo(_ resolve: @escaping RCTPromiseResolveBlock,
                     rejecter reject: @escaping RCTPromiseRejectBlock) {
    let info: [String: Any] = [
      "model": UIDevice.current.model,
      "systemVersion": UIDevice.current.systemVersion,
      "name": UIDevice.current.name
    ]
    resolve(info)
  }

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }
}

// ios/MyModule.m (Bridge)
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyModule, NSObject)

RCT_EXTERN_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)

@end

// Android - Native module in Kotlin
// android/app/src/main/java/com/myapp/MyModule.kt
package com.myapp

import com.facebook.react.bridge.*

class MyModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    override fun getName() = "MyModule"

    @ReactMethod
    fun getDeviceInfo(promise: Promise) {
        val info = Arguments.createMap().apply {
            putString("model", android.os.Build.MODEL)
            putString("systemVersion", android.os.Build.VERSION.RELEASE)
            putString("manufacturer", android.os.Build.MANUFACTURER)
        }
        promise.resolve(info)
    }
}

// JavaScript - Using native module
import { NativeModules } from 'react-native'

const { MyModule } = NativeModules

async function getDeviceInfo() {
  try {
    const info = await MyModule.getDeviceInfo()
    console.log('Device info:', info)
    return info
  } catch (error) {
    console.error('Error getting device info:', error)
    throw error
  }
}

20. Apa itu Expo dan kapan sebaiknya digunakan?

Expo merupakan framework yang menyederhanakan pengembangan React Native dengan mengelola konfigurasi native.

jsx
// Expo benefits
// - No need for Android Studio or Xcode to start
// - OTA (over-the-air) updates without app stores
// - Rich SDK with preconfigured modules
// - EAS Build for cloud builds

// Creating an Expo project
// npx create-expo-app@latest MyApp

// Using Expo modules
import * as ImagePicker from 'expo-image-picker'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'

async function pickImage() {
  // Request permission
  const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync()

  if (status !== 'granted') {
    alert('Permission denied')
    return
  }

  // Open image picker
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [4, 3],
    quality: 0.8
  })

  if (!result.canceled) {
    return result.assets[0].uri
  }
}

// Build configuration
// app.json
{
  "expo": {
    "name": "MyApp",
    "slug": "myapp",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.company.myapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "package": "com.company.myapp"
    },
    "plugins": [
      "expo-router",
      [
        "expo-camera",
        {
          "cameraPermission": "Allow camera access"
        }
      ]
    ]
  }
}

// When NOT to use Expo
// - Need for complex custom native modules
// - Integration with specific native SDKs
// - Total control over native configuration
// - Very lightweight application (Expo adds weight)

21. Bagaimana cara kerja Hot Reloading?

Hot Reloading (Fast Refresh) memungkinkan perubahan diterapkan secara langsung tanpa kehilangan state aplikasi.

jsx
// Fast Refresh preserves hooks state
function Counter() {
  const [count, setCount] = useState(0)

  // Modify this text and save
  // The count state will be preserved
  return (
    <View>
      <Text>Counter: {count}</Text>
      <Button title="+1" onPress={() => setCount(c => c + 1)} />
    </View>
  )
}

// ⚠️ Cases where Fast Refresh does full reload
// 1. Syntax error
// 2. Modifying a class component
// 3. File with mixed non-component exports

// ❌ This file will do full reload because of mixed exports
export const API_URL = 'https://api.example.com'
export function MyComponent() { /* ... */ }

// ✅ Separate into distinct files
// constants.js
export const API_URL = 'https://api.example.com'

// MyComponent.js
export function MyComponent() { /* ... */ }

// Force remount if needed
// Add this comment at top of file:
// @refresh reset

// Metro configuration for Fast Refresh
// metro.config.js
module.exports = {
  transformer: {
    experimentalImportSupport: false,
    inlineRequires: true
  }
}

Pengujian dan Kualitas

22. Bagaimana cara menguji aplikasi React Native?

Pengujian React Native menggunakan Jest beserta pustaka khusus untuk rendering dan interaksi.

jsx
// Jest configuration
// jest.config.js
module.exports = {
  preset: 'react-native',
  setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
  transformIgnorePatterns: [
    'node_modules/(?!(react-native|@react-native|@react-navigation)/)'
  ],
  moduleNameMapper: {
    '\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js'
  }
}

// Component test with React Native Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native'
import { ProductCard } from './ProductCard'

describe('ProductCard', () => {
  const mockProduct = {
    id: '1',
    name: 'iPhone 15',
    price: 999,
    image: 'https://example.com/iphone.jpg'
  }

  it('renders product information', () => {
    render(<ProductCard product={mockProduct} />)

    expect(screen.getByText('iPhone 15')).toBeOnTheScreen()
    expect(screen.getByText('$999')).toBeOnTheScreen()
  })

  it('calls onPress when tapped', () => {
    const onPress = jest.fn()
    render(<ProductCard product={mockProduct} onPress={onPress} />)

    fireEvent.press(screen.getByTestId('product-card'))

    expect(onPress).toHaveBeenCalledWith('1')
  })

  it('shows loading state when adding to cart', async () => {
    const onAddToCart = jest.fn(() => new Promise(r => setTimeout(r, 100)))
    render(<ProductCard product={mockProduct} onAddToCart={onAddToCart} />)

    fireEvent.press(screen.getByText('Add to cart'))

    expect(screen.getByTestId('loading-indicator')).toBeOnTheScreen()

    await waitFor(() => {
      expect(screen.queryByTestId('loading-indicator')).not.toBeOnTheScreen()
    })
  })
})

// Navigation test
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'

const Stack = createNativeStackNavigator()

function renderWithNavigation(component, { initialRouteName = 'Test' } = {}) {
  return render(
    <NavigationContainer>
      <Stack.Navigator initialRouteName={initialRouteName}>
        <Stack.Screen name="Test" component={component} />
        <Stack.Screen name="Detail" component={DetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}

// Custom hook test
import { renderHook, act } from '@testing-library/react-native'
import { useCounter } from './useCounter'

test('increments counter', () => {
  const { result } = renderHook(() => useCounter(0))

  act(() => {
    result.current.increment()
  })

  expect(result.current.count).toBe(1)
})

23. Bagaimana cara mengimplementasikan pengujian E2E dengan Detox?

Detox memungkinkan pengujian aplikasi pada simulator atau emulator nyata.

jsx
// Installation
// npm install detox --save-dev
// detox init -r jest

// .detoxrc.js
module.exports = {
  testRunner: {
    args: {
      $0: 'jest',
      config: 'e2e/jest.config.js'
    },
    jest: {
      setupTimeout: 120000
    }
  },
  apps: {
    'ios.debug': {
      type: 'ios.app',
      binaryPath: 'ios/build/MyApp.app',
      build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
    },
    'android.debug': {
      type: 'android.apk',
      binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
      build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug'
    }
  },
  devices: {
    simulator: {
      type: 'ios.simulator',
      device: { type: 'iPhone 15' }
    },
    emulator: {
      type: 'android.emulator',
      device: { avdName: 'Pixel_5_API_34' }
    }
  }
}

// e2e/login.test.js
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp()
  })

  beforeEach(async () => {
    await device.reloadReactNative()
  })

  it('should login successfully with valid credentials', async () => {
    // Fill the form
    await element(by.id('email-input')).typeText('test@example.com')
    await element(by.id('password-input')).typeText('password123')

    // Submit
    await element(by.id('login-button')).tap()

    // Verify navigation to dashboard
    await waitFor(element(by.text('Welcome')))
      .toBeVisible()
      .withTimeout(5000)
  })

  it('should show error with invalid credentials', async () => {
    await element(by.id('email-input')).typeText('wrong@example.com')
    await element(by.id('password-input')).typeText('wrongpassword')
    await element(by.id('login-button')).tap()

    await expect(element(by.text('Invalid credentials'))).toBeVisible()
  })

  it('should navigate to forgot password', async () => {
    await element(by.id('forgot-password-link')).tap()

    await expect(element(by.text('Reset Password'))).toBeVisible()
  })
})

// Detox commands
// detox build --configuration ios.debug
// detox test --configuration ios.debug

Deployment dan Produksi

24. Bagaimana cara mengelola environment (dev, staging, prod)?

Manajemen environment memerlukan variabel konfigurasi yang berbeda untuk masing-masing tahap.

jsx
// Option 1: react-native-config
// .env.development
API_URL=https://dev-api.myapp.com
ANALYTICS_KEY=dev_key

// .env.staging
API_URL=https://staging-api.myapp.com
ANALYTICS_KEY=staging_key

// .env.production
API_URL=https://api.myapp.com
ANALYTICS_KEY=prod_key

// Usage
import Config from 'react-native-config'

const api = {
  baseUrl: Config.API_URL,
  analyticsKey: Config.ANALYTICS_KEY
}

// Option 2: JavaScript configuration file
// config/index.js
const ENV = {
  development: {
    apiUrl: 'https://dev-api.myapp.com',
    analyticsEnabled: false,
    logLevel: 'debug'
  },
  staging: {
    apiUrl: 'https://staging-api.myapp.com',
    analyticsEnabled: true,
    logLevel: 'info'
  },
  production: {
    apiUrl: 'https://api.myapp.com',
    analyticsEnabled: true,
    logLevel: 'error'
  }
}

const getEnv = () => {
  if (__DEV__) return 'development'
  // Logic to determine staging vs prod
  return 'production'
}

export const config = ENV[getEnv()]

// Option 3: Expo with app.config.js
// app.config.js
export default ({ config }) => {
  const env = process.env.APP_ENV || 'development'

  const envConfig = {
    development: {
      apiUrl: 'https://dev-api.myapp.com',
      bundleId: 'com.myapp.dev'
    },
    production: {
      apiUrl: 'https://api.myapp.com',
      bundleId: 'com.myapp'
    }
  }

  return {
    ...config,
    extra: {
      ...envConfig[env],
      env
    },
    ios: {
      bundleIdentifier: envConfig[env].bundleId
    },
    android: {
      package: envConfig[env].bundleId
    }
  }
}

25. Bagaimana cara melakukan deployment ke app store?

Deployment melibatkan konfigurasi build, metadata, serta proses pengajuan.

bash
# Option 1: EAS Build (Expo)
# Installation
npm install -g eas-cli

# Configuration
eas build:configure

# eas.json
{
  "cli": {
    "version": ">= 5.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "android": {
        "buildType": "apk"
      }
    },
    "production": {
      "autoIncrement": true
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "your@email.com",
        "ascAppId": "1234567890"
      },
      "android": {
        "serviceAccountKeyPath": "./google-services.json",
        "track": "production"
      }
    }
  }
}

# Production build
eas build --platform all --profile production

# Store submission
eas submit --platform all --profile production

# Option 2: Fastlane (React Native CLI)
# Gemfile
source "https://rubygems.org"
gem "fastlane"

# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Deploy to TestFlight"
  lane :beta do
    increment_build_number(xcodeproj: "MyApp.xcodeproj")
    build_app(scheme: "MyApp")
    upload_to_testflight
  end

  desc "Deploy to App Store"
  lane :release do
    increment_build_number(xcodeproj: "MyApp.xcodeproj")
    build_app(scheme: "MyApp")
    upload_to_app_store(
      skip_screenshots: true,
      skip_metadata: true
    )
  end
end

# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "Deploy to Play Store internal"
  lane :beta do
    gradle(task: "clean bundleRelease")
    upload_to_play_store(
      track: "internal",
      aab: "app/build/outputs/bundle/release/app-release.aab"
    )
  end
end

26. Bagaimana cara mengimplementasikan OTA update?

Over-The-Air update memungkinkan distribusi kode JavaScript baru tanpa melalui app store.

jsx
// With Expo Updates
import * as Updates from 'expo-updates'

async function checkForUpdates() {
  if (__DEV__) return // Not in development

  try {
    const update = await Updates.checkForUpdateAsync()

    if (update.isAvailable) {
      // Download update
      await Updates.fetchUpdateAsync()

      // Ask user to restart
      Alert.alert(
        'Update available',
        'A new version is available. Restart now?',
        [
          { text: 'Later', style: 'cancel' },
          {
            text: 'Restart',
            onPress: () => Updates.reloadAsync()
          }
        ]
      )
    }
  } catch (error) {
    console.error('Update check error:', error)
  }
}

// Automatic check on startup
function App() {
  useEffect(() => {
    checkForUpdates()
  }, [])

  return <AppNavigator />
}

// eas.json configuration for update channels
{
  "build": {
    "production": {
      "channel": "production"
    },
    "preview": {
      "channel": "preview"
    }
  }
}

// Command to publish an update
// eas update --branch production --message "Bug fix"

// With CodePush (Microsoft)
import codePush from 'react-native-code-push'

const codePushOptions = {
  checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
  installMode: codePush.InstallMode.ON_NEXT_RESTART
}

function App() {
  return <AppNavigator />
}

export default codePush(codePushOptions)(App)

Pertanyaan Lanjutan

27. Bagaimana cara mengoptimalkan waktu startup aplikasi?

Waktu startup merupakan faktor kritis terhadap pengalaman pengguna.

1. Enable Hermes (optimized JS engine)jsx
// android/app/build.gradle
project.ext.react = [
    enableHermes: true
]

// ios/Podfile
:hermes_enabled => true

// 2. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))

// 3. Defer non-critical initializations
import { InteractionManager } from 'react-native'

function App() {
  useEffect(() => {
    // Execute after first render
    InteractionManager.runAfterInteractions(() => {
      // Initialize analytics
      Analytics.init()
      // Prefetch data
      prefetchCriticalData()
    })
  }, [])

  return <AppNavigator />
}

// 4. Optimize splash screen
import * as SplashScreen from 'expo-splash-screen'

// Prevent automatic hiding
SplashScreen.preventAutoHideAsync()

function App() {
  const [appIsReady, setAppIsReady] = useState(false)

  useEffect(() => {
    async function prepare() {
      try {
        // Load critical resources
        await Font.loadAsync(customFonts)
        await Image.prefetch(criticalImages)
        // Restore authentication
        await restoreAuth()
      } catch (e) {
        console.warn(e)
      } finally {
        setAppIsReady(true)
      }
    }

    prepare()
  }, [])

  const onLayoutRootView = useCallback(async () => {
    if (appIsReady) {
      // Hide splash screen
      await SplashScreen.hideAsync()
    }
  }, [appIsReady])

  if (!appIsReady) return null

  return (
    <View style={{ flex: 1 }} onLayout={onLayoutRootView}>
      <AppNavigator />
    </View>
  )
}

// 5. Inline requires to defer imports
// metro.config.js
module.exports = {
  transformer: {
    inlineRequires: true
  }
}

// Manual usage
function loadHeavyModule() {
  const HeavyModule = require('./HeavyModule').default
  return HeavyModule
}

28. Bagaimana cara menangani keamanan pada aplikasi React Native?

Keamanan mobile membutuhkan beberapa lapisan perlindungan.

1. Secure storage for sensitive datajsx
import * as SecureStore from 'expo-secure-store'
// or
import * as Keychain from 'react-native-keychain'

async function saveToken(token) {
  await SecureStore.setItemAsync('auth_token', token)
}

async function getToken() {
  return await SecureStore.getItemAsync('auth_token')
}

// 2. Certificate pinning for network calls
// android/app/src/main/res/xml/network_security_config.xml
/*
<network-security-config>
  <domain-config>
    <domain includeSubdomains="true">api.myapp.com</domain>
    <pin-set>
      <pin digest="SHA-256">AAAAAA...</pin>
      <pin digest="SHA-256">BBBBBB...</pin>
    </pin-set>
  </domain-config>
</network-security-config>
*/

// 3. Jailbreak/root detection
import JailMonkey from 'jail-monkey'

function SecurityCheck() {
  useEffect(() => {
    if (JailMonkey.isJailBroken()) {
      Alert.alert(
        'Insecure device',
        'This application cannot run on a rooted/jailbroken device'
      )
    }
  }, [])
}

// 4. Code obfuscation
// metro.config.js (for Hermes)
module.exports = {
  transformer: {
    minifierConfig: {
      mangle: true,
      output: {
        ascii_only: true
      }
    }
  }
}

// 5. Screenshot/recording protection
import { usePreventScreenCapture } from 'expo-screen-capture'

function SensitiveScreen() {
  usePreventScreenCapture() // iOS only

  return <View>{/* Sensitive data */}</View>
}

// 6. Session timeout
function useSessionTimeout(timeoutMs = 5 * 60 * 1000) {
  const lastActivity = useRef(Date.now())
  const { logout } = useAuth()

  useEffect(() => {
    const subscription = AppState.addEventListener('change', (state) => {
      if (state === 'active') {
        const elapsed = Date.now() - lastActivity.current
        if (elapsed > timeoutMs) {
          logout()
        }
      } else {
        lastActivity.current = Date.now()
      }
    })

    return () => subscription.remove()
  }, [timeoutMs, logout])
}

29. Bagaimana cara mengimplementasikan aksesibilitas?

Aksesibilitas merupakan hal penting agar aplikasi dapat digunakan oleh semua orang.

jsx
import { AccessibilityInfo } from 'react-native'

// 1. Basic accessibility props
function AccessibleButton({ label, onPress, disabled }) {
  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled}
      accessible={true}
      accessibilityLabel={label}
      accessibilityRole="button"
      accessibilityState={{ disabled }}
      accessibilityHint={`Tap to ${label.toLowerCase()}`}
    >
      <Text>{label}</Text>
    </TouchableOpacity>
  )
}

// 2. Group elements for screen readers
function ProductCard({ product }) {
  return (
    <View
      accessible={true}
      accessibilityLabel={`${product.name}, ${product.price} dollars`}
    >
      <Image
        source={{ uri: product.image }}
        accessibilityIgnoresInvertColors={true}
      />
      <Text>{product.name}</Text>
      <Text>${product.price}</Text>
    </View>
  )
}

// 3. Announce dynamic changes
function NotificationBadge({ count }) {
  useEffect(() => {
    if (count > 0) {
      AccessibilityInfo.announceForAccessibility(
        `${count} new notification${count > 1 ? 's' : ''}`
      )
    }
  }, [count])

  return (
    <View accessibilityLabel={`${count} notifications`}>
      <Text>{count}</Text>
    </View>
  )
}

// 4. Detect accessibility preferences
function useAccessibilityPreferences() {
  const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false)
  const [isReduceMotionEnabled, setIsReduceMotionEnabled] = useState(false)

  useEffect(() => {
    AccessibilityInfo.isScreenReaderEnabled().then(setIsScreenReaderEnabled)
    AccessibilityInfo.isReduceMotionEnabled().then(setIsReduceMotionEnabled)

    const screenReaderListener = AccessibilityInfo.addEventListener(
      'screenReaderChanged',
      setIsScreenReaderEnabled
    )
    const reduceMotionListener = AccessibilityInfo.addEventListener(
      'reduceMotionChanged',
      setIsReduceMotionEnabled
    )

    return () => {
      screenReaderListener.remove()
      reduceMotionListener.remove()
    }
  }, [])

  return { isScreenReaderEnabled, isReduceMotionEnabled }
}

// 5. Adapt animations based on preferences
function AnimatedComponent() {
  const { isReduceMotionEnabled } = useAccessibilityPreferences()

  const animation = useSharedValue(0)

  useEffect(() => {
    animation.value = withTiming(1, {
      duration: isReduceMotionEnabled ? 0 : 300
    })
  }, [isReduceMotionEnabled])

  return <Animated.View style={animatedStyle} />
}

30. Bagaimana cara menyusun proyek React Native berskala besar?

Arsitektur yang terstruktur memudahkan pemeliharaan dan skalabilitas aplikasi.

text
// Recommended structure
src/
├── app/                    # App configuration
│   ├── App.tsx
│   ├── Navigation.tsx
│   └── Providers.tsx
├── features/               # Feature modules
│   ├── auth/
│   │   ├── screens/
│   │   │   ├── LoginScreen.tsx
│   │   │   └── RegisterScreen.tsx
│   │   ├── components/
│   │   │   └── AuthForm.tsx
│   │   ├── hooks/
│   │   │   └── useAuth.ts
│   │   ├── services/
│   │   │   └── authService.ts
│   │   └── index.ts        # Public export
│   │
│   ├── products/
│   │   ├── screens/
│   │   ├── components/
│   │   ├── hooks/
│   │   └── services/
│   │
│   └── cart/
│       └── ...
├── shared/                 # Shared code
│   ├── components/
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   └── Card.tsx
│   ├── hooks/
│   │   ├── useDebounce.ts
│   │   └── useNetworkStatus.ts
│   ├── utils/
│   │   ├── format.ts
│   │   └── validation.ts
│   └── types/
│       └── index.ts
├── services/               # Global services
│   ├── api/
│   │   ├── client.ts
│   │   └── interceptors.ts
│   ├── storage/
│   │   └── secureStorage.ts
│   └── analytics/
│       └── analytics.ts
├── store/                  # Global state
│   ├── slices/
│   └── index.ts
└── theme/                  # Design system
    ├── colors.ts
    ├── typography.ts
    ├── spacing.ts
    └── index.ts
jsx
// Example feature module organization
// features/products/index.ts
export { ProductListScreen } from './screens/ProductListScreen'
export { ProductDetailScreen } from './screens/ProductDetailScreen'
export { useProducts } from './hooks/useProducts'
export { ProductCard } from './components/ProductCard'

// features/products/hooks/useProducts.ts
import { useQuery } from '@tanstack/react-query'
import { productService } from '../services/productService'

export function useProducts(categoryId?: string) {
  return useQuery({
    queryKey: ['products', categoryId],
    queryFn: () => productService.getProducts(categoryId)
  })
}

// features/products/services/productService.ts
import { apiClient } from '@/services/api/client'
import { Product } from '../types'

export const productService = {
  async getProducts(categoryId?: string): Promise<Product[]> {
    const params = categoryId ? { category: categoryId } : {}
    const response = await apiClient.get('/products', { params })
    return response.data
  },

  async getProduct(id: string): Promise<Product> {
    const response = await apiClient.get(`/products/${id}`)
    return response.data
  }
}

// Import alias configuration
// babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        alias: {
          '@': './src',
          '@features': './src/features',
          '@shared': './src/shared',
          '@services': './src/services'
        }
      }
    ]
  ]
}

Kesimpulan

30 pertanyaan ini mencakup pengetahuan inti React Native yang umum diharapkan dalam wawancara. Beberapa poin utama yang perlu dikuasai:

  • Arsitektur: Memahami bridge, JSI, dan arsitektur baru
  • Komponen: Navigasi, daftar berperforma tinggi, animasi
  • State dan data: Zustand/Redux, React Query, penyimpanan lokal
  • Performa: Optimasi FlatList, memoisasi, profiling
  • Kekhususan mobile: Permission, notifikasi, deep link
  • Pengujian: Jest, Testing Library, Detox
  • Deployment: EAS Build, app store, OTA update
  • Keamanan dan aksesibilitas: Penyimpanan aman, kepatuhan WCAG

Persiapan wawancara React Native menuntut pemahaman aspek mobile yang melampaui pengetahuan React web. Latihan menggunakan proyek nyata dan pengujian pada perangkat sebenarnya akan memperkuat penguasaan konsep-konsep tersebut.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tag

#react native interview
#mobile interview
#react native questions
#javascript
#technical interview

Bagikan

Artikel terkait