November 22, 2025
19 min read

Senior React Native Developer Interview Questions: Complete Guide

interview
career-advice
job-search
Senior React Native Developer Interview Questions: Complete Guide
MB

Milad Bonakdar

Author

Master advanced React Native development with essential interview questions covering performance optimization, native modules, state management, testing, architecture patterns, and cross-platform best practices for senior developers.


Introduction

Senior React Native developers are expected to architect scalable cross-platform applications, optimize performance, integrate native modules, and make informed architectural decisions. This role demands deep expertise in React Native, state management, native development, and the ability to solve complex mobile development challenges.

This comprehensive guide covers essential interview questions for Senior React Native Developers, spanning advanced React concepts, performance optimization, state management, native modules, testing, and architectural patterns. Each question includes detailed answers, rarity assessment, and difficulty ratings.


Advanced React & Hooks (5 Questions)

1. Explain useMemo and useCallback. When should you use them?

Answer: Both hooks optimize performance by memoizing values/functions.

  • useMemo: Memoizes computed values (expensive calculations)
  • useCallback: Memoizes function references (prevents recreation)
  • When to use: Only when you have performance issues. Premature optimization can make code harder to read.
import { useMemo, useCallback, useState } from 'react';

function ExpensiveComponent({ items, onItemClick }) {
  // useMemo - memoize expensive calculation
  const sortedItems = useMemo(() => {
    console.log('Sorting items...');
    return items.sort((a, b) => a.price - b.price);
  }, [items]); // Only recalculate when items change
  
  // useCallback - memoize function
  const handleClick = useCallback((id) => {
    console.log('Item clicked:', id);
    onItemClick(id);
  }, [onItemClick]); // Only recreate when onItemClick changes
  
  return (
    <FlatList
      data={sortedItems}
      renderItem={({ item }) => (
        <ItemRow item={item} onClick={handleClick} />
      )}
    />
  );
}

// Child component with React.memo
const ItemRow = React.memo(({ item, onClick }) => {
  console.log('Rendering item:', item.id);
  return (
    <TouchableOpacity onPress={() => onClick(item.id)}>
      <Text>{item.name}</Text>
    </TouchableOpacity>
  );
});

Rarity: Very Common Difficulty: Medium


2. What is useRef and what are its use cases?

Answer: useRef creates a mutable reference that persists across renders without causing re-renders.

  • Use Cases:
    • Accessing DOM/native elements
    • Storing mutable values without triggering re-render
    • Keeping previous values
    • Storing timers/intervals
import { useRef, useEffect } from 'react';
import { TextInput, Animated } from 'react-native';

function FormComponent() {
  const inputRef = useRef(null);
  const previousValue = useRef('');
  const animatedValue = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    // Focus input on mount
    inputRef.current?.focus();
  }, []);
  
  const handleChange = (text) => {
    console.log('Previous:', previousValue.current);
    console.log('Current:', text);
    previousValue.current = text;
  };
  
  const startAnimation = () => {
    Animated.timing(animatedValue, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();
  };
  
  return (
    <View>
      <TextInput ref={inputRef} onChangeText={handleChange} />
      <Animated.View style={{ opacity: animatedValue }}>
        <Text>Animated Content</Text>
      </Animated.View>
    </View>
  );
}

// Timer example
function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    
    return () => clearInterval(intervalRef.current);
  }, []);
  
  return <Text>{count}</Text>;
}

Rarity: Common Difficulty: Medium


3. Explain Custom Hooks and when to create them.

Answer: Custom hooks extract reusable stateful logic into separate functions.

  • Benefits: Code reuse, separation of concerns, easier testing
  • Convention: Must start with "use"
// Custom hook for API fetching
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

// Custom hook for form handling
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  
  const handleChange = (name, value) => {
    setValues(prev => ({ ...prev, [name]: value }));
  };
  
  const validate = (validationRules) => {
    const newErrors = {};
    Object.keys(validationRules).forEach(field => {
      const rule = validationRules[field];
      if (rule.required && !values[field]) {
        newErrors[field] = `${field} is required`;
      }
    });
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  return { values, errors, handleChange, validate };
}

// Usage
function UserProfile() {
  const { data, loading, error } = useFetch('https://api.example.com/user');
  const { values, errors, handleChange, validate } = useForm({
    name: '',
    email: '',
  });
  
  if (loading) return <ActivityIndicator />;
  if (error) return <Text>Error: {error}</Text>;
  
  return <Text>{data.name}</Text>;
}

Rarity: Common Difficulty: Medium


4. What is React Context and when should you use it?

Answer: Context provides a way to pass data through the component tree without prop drilling.

  • Use Cases: Theme, authentication, language preferences
  • Caution: Can cause unnecessary re-renders if not used carefully
import { createContext, useContext, useState } from 'react';

// Create context
const ThemeContext = createContext();
const AuthContext = createContext();

// Theme Provider
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Auth Provider
function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  
  const login = async (credentials) => {
    const user = await loginAPI(credentials);
    setUser(user);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Custom hooks for context
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

function useAuth() {
  return useContext(AuthContext);
}

// Usage
function App() {
  return (
    <ThemeProvider>
      <AuthProvider>
        <MainApp />
      </AuthProvider>
    </ThemeProvider>
  );
}

function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  const { user } = useAuth();
  
  return (
    <TouchableOpacity
      style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}
      onPress={toggleTheme}
    >
      <Text>{user ? `Hello, ${user.name}` : 'Guest'}</Text>
    </TouchableOpacity>
  );
}

Rarity: Very Common Difficulty: Medium


5. Explain the difference between useEffect and useLayoutEffect.

Answer: Both run side effects, but at different times:

  • useEffect: Runs asynchronously after render is painted to screen
  • useLayoutEffect: Runs synchronously before paint (blocks visual updates)
  • Use useLayoutEffect when: You need to measure DOM or prevent visual flicker
import { useEffect, useLayoutEffect, useRef, useState } from 'react';

function MeasureComponent() {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);
  
  // useLayoutEffect - measure before paint
  useLayoutEffect(() => {
    if (elementRef.current) {
      const { height } = elementRef.current.measure((x, y, width, height) => {
        setHeight(height);
      });
    }
  }, []);
  
  // useEffect - after paint
  useEffect(() => {
    console.log('Component rendered');
  }, []);
  
  return (
    <View ref={elementRef}>
      <Text>Height: {height}</Text>
    </View>
  );
}

Rarity: Medium Difficulty: Hard


State Management (4 Questions)

6. Explain Redux and its core principles.

Answer: Redux is a predictable state container for JavaScript apps.

Loading diagram...
  • Core Principles:
    • Single source of truth (one store)
    • State is read-only (dispatch actions to change)
    • Changes made with pure functions (reducers)
// Action types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';

// Action creators
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { id: Date.now(), text, completed: false },
});

const toggleTodo = (id) => ({
  type: TOGGLE_TODO,
  payload: id,
});

// Reducer
const todosReducer = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, action.payload];
    case TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    default:
      return state;
  }
};

// Store
import { createStore } from 'redux';
const store = createStore(todosReducer);

// React Native component
import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();
  
  return (
    <View>
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => dispatch(toggleTodo(item.id))}>
            <Text style={{ textDecorationLine: item.completed ? 'line-through' : 'none' }}>
              {item.text}
            </Text>
          </TouchableOpacity>
        )}
      />
      <Button title="Add" onPress={() => dispatch(addTodo('New Task'))} />
    </View>
  );
}

Rarity: Very Common Difficulty: Hard


7. What is Redux Toolkit and how does it simplify Redux?

Answer: Redux Toolkit is the official recommended way to write Redux logic.

  • Benefits:
    • Less boilerplate
    • Built-in Immer for immutable updates
    • Includes Redux Thunk
    • Better TypeScript support
import { createSlice, configureStore } from '@reduxjs/toolkit';

// Slice (combines actions and reducer)
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      // Immer allows "mutating" code
      state.push({
        id: Date.now(),
        text: action.payload,
        completed: false,
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
  },
});

export const { addTodo, toggleTodo } = todosSlice.actions;

// Store
const store = configureStore({
  reducer: {
    todos: todosSlice.reducer,
  },
});

// Async thunk
import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await fetch('https://api.example.com/todos');
    return response.json();
  }
);

const todosSlice = createSlice({
  name: 'todos',
  initialState: { items: [], loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

Rarity: Common Difficulty: Medium


8. What are alternatives to Redux for state management?

Answer: Multiple state management solutions exist:

  • Context API + useReducer: Built-in, good for simple apps
  • MobX: Observable-based, less boilerplate
  • Zustand: Minimal, hooks-based
  • Recoil: Atom-based, by Facebook
  • Jotai: Primitive atoms
// Zustand example
import create from 'zustand';

const useStore = create((set) => ({
  todos: [],
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: Date.now(), text, completed: false }],
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ),
  })),
}));

// Usage
function TodoList() {
  const { todos, addTodo, toggleTodo } = useStore();
  
  return (
    <View>
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => toggleTodo(item.id)}>
            <Text>{item.text}</Text>
          </TouchableOpacity>
        )}
      />
    </View>
  );
}

Rarity: Common Difficulty: Medium


9. How do you handle side effects in Redux?

Answer: Use middleware for async operations:

  • Redux Thunk: Functions that return functions
  • Redux Saga: Generator-based, more powerful
  • Redux Observable: RxJS-based
// Redux Thunk
const fetchUser = (userId) => async (dispatch) => {
  dispatch({ type: 'FETCH_USER_REQUEST' });
  
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    const user = await response.json();
    dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (error) {
    dispatch({ type: 'FETCH_USER_FAILURE', payload: error.message });
  }
};

// Usage
dispatch(fetchUser(123));

// Redux Saga
import { call, put, takeEvery } from 'redux-saga/effects';

function* fetchUserSaga(action) {
  try {
    const user = yield call(fetch, `https://api.example.com/users/${action.payload}`);
    const data = yield call([user, 'json']);
    yield put({ type: 'FETCH_USER_SUCCESS', payload: data });
  } catch (error) {
    yield put({ type: 'FETCH_USER_FAILURE', payload: error.message });
  }
}

function* watchFetchUser() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUserSaga);
}

Rarity: Common Difficulty: Hard


Performance Optimization (5 Questions)

10. How do you optimize FlatList performance?

Answer: Multiple strategies improve FlatList scrolling:

  1. Use keyExtractor: Provide unique keys
  2. getItemLayout: Skip measurement for fixed-height items
  3. removeClippedSubviews: Unmount off-screen views (Android)
  4. maxToRenderPerBatch: Control batch size
  5. windowSize: Control rendered window
  6. initialNumToRender: Items to render initially
  7. Memoize renderItem: Prevent unnecessary re-renders
import React, { memo, useCallback } from 'react';

const ITEM_HEIGHT = 80;

// Memoized item component
const ListItem = memo(({ item, onPress }) => {
  return (
    <TouchableOpacity onPress={() => onPress(item.id)} style={{ height: ITEM_HEIGHT }}>
      <Text>{item.name}</Text>
    </TouchableOpacity>
  );
});

function OptimizedList({ data }) {
  const handlePress = useCallback((id) => {
    console.log('Pressed:', id);
  }, []);
  
  const renderItem = useCallback(({ item }) => (
    <ListItem item={item} onPress={handlePress} />
  ), [handlePress]);
  
  const keyExtractor = useCallback((item) => item.id.toString(), []);
  
  const getItemLayout = useCallback((data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  }), []);
  
  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      getItemLayout={getItemLayout}
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={5}
    />
  );
}

Rarity: Very Common Difficulty: Medium


11. What is the React Native bridge and how does it affect performance?

Answer: The bridge is the communication layer between JavaScript and native code.

  • How it works:
    • JavaScript runs in a separate thread
    • Native modules run in native threads
    • Bridge serializes data between them (JSON)
  • Performance Impact:
    • Bridge communication is asynchronous
    • Serialization overhead
    • Can become bottleneck with frequent communication
  • Solutions:
    • Minimize bridge crossings
    • Batch operations
    • Use native animations (bypass bridge)
    • New Architecture (JSI) removes bridge
// Bad - frequent bridge crossings
const BadAnimation = () => {
  const [position, setPosition] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setPosition(p => p + 1); // Bridge crossing every frame!
    }, 16);
    return () => clearInterval(interval);
  }, []);
  
  return <View style={{ transform: [{ translateX: position }] }} />;
};

// Good - native animation (no bridge)
const GoodAnimation = () => {
  const translateX = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.timing(translateX, {
      toValue: 100,
      duration: 1000,
      useNativeDriver: true, // Runs on native thread!
    }).start();
  }, []);
  
  return <Animated.View style={{ transform: [{ translateX }] }} />;
};

Rarity: Common Difficulty: Hard


12. How do you prevent unnecessary re-renders?

Answer: Multiple techniques prevent wasted renders:

  1. React.memo: Memoize components
  2. useMemo/useCallback: Memoize values/functions
  3. Proper key props: Help React identify changes
  4. Avoid inline objects/arrays: Create new references
  5. Split components: Smaller, focused components
// Bad - creates new object every render
function BadComponent() {
  return <ChildComponent style={{ margin: 10 }} />; // New object!
}

// Good - stable reference
const styles = StyleSheet.create({
  container: { margin: 10 },
});

function GoodComponent() {
  return <ChildComponent style={styles.container} />;
}

// React.memo with custom comparison
const ExpensiveComponent = React.memo(
  ({ data, onPress }) => {
    console.log('Rendering ExpensiveComponent');
    return (
      <View>
        <Text>{data.name}</Text>
        <Button onPress={onPress} />
      </View>
    );
  },
  (prevProps, nextProps) => {
    // Custom comparison - only re-render if data.id changed
    return prevProps.data.id === nextProps.data.id;
  }
);

// Split components to isolate re-renders
function ParentComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  return (
    <View>
      {/* Only re-renders when count changes */}
      <CountDisplay count={count} />
      
      {/* Only re-renders when text changes */}
      <TextDisplay text={text} />
      
      <Button onPress={() => setCount(c => c + 1)} />
    </View>
  );
}

const CountDisplay = React.memo(({ count }) => <Text>{count}</Text>);
const TextDisplay = React.memo(({ text }) => <Text>{text}</Text>);

Rarity: Very Common Difficulty: Medium


13. How do you optimize images in React Native?

Answer: Image optimization is crucial for performance:

  1. Resize images: Use appropriate dimensions
  2. Cache images: Use libraries like react-native-fast-image
  3. Lazy loading: Load images on demand
  4. Progressive loading: Show placeholder first
  5. Use WebP format: Better compression
import FastImage from 'react-native-fast-image';

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

// Progressive image loading
function ProgressiveImage({ thumbnailUri, fullUri }) {
  const [imageLoaded, setImageLoaded] = useState(false);
  
  return (
    <View>
      <Image
        source={{ uri: thumbnailUri }}
        style={styles.image}
        blurRadius={imageLoaded ? 0 : 5}
      />
      <Image
        source={{ uri: fullUri }}
        style={[styles.image, { opacity: imageLoaded ? 1 : 0 }]}
        onLoad={() => setImageLoaded(true)}
      />
    </View>
  );
}

// Image prefetching
import { Image } from 'react-native';

const prefetchImages = async (urls) => {
  const promises = urls.map(url => Image.prefetch(url));
  await Promise.all(promises);
};

Rarity: Common Difficulty: Medium


14. What tools do you use for performance profiling?

Answer: Multiple tools help identify performance issues:

  • React DevTools Profiler: Component render times
  • Flipper: Debugging and profiling tool
  • Performance Monitor: Built-in FPS monitor
  • Systrace: Android performance tracing
  • Instruments: iOS performance profiling
// Enable performance monitor in dev
import { LogBox } from 'react-native';

if (__DEV__) {
  // Show performance monitor
  require('react-native').unstable_enableLogBox();
}

// Measure component render time
import { Profiler } from 'react';

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
}

// Custom performance tracking
const measurePerformance = (name, fn) => {
  const start = performance.now();
  const result = fn();
  const end = performance.now();
  console.log(`${name} took ${end - start}ms`);
  return result;
};

Rarity: Common Difficulty: Medium


Native Modules & Platform-Specific (4 Questions)

15. How do you create a Native Module in React Native?

Answer: Native modules allow you to use platform-specific code.

// iOS (Objective-C) - CalendarManager.m
#import "CalendarManager.h"
#import <React/RCTLog.h>

@implementation CalendarManager

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
  RCTLogInfo(@"Creating event %@ at %@", name, location);
}

@end

// Android (Java) - CalendarModule.java
package com.myapp;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class CalendarModule extends ReactContextBaseJavaModule {
  CalendarModule(ReactApplicationContext context) {
    super(context);
  }

  @Override
  public String getName() {
    return "CalendarManager";
  }

  @ReactMethod
  public void addEvent(String name, String location) {
    Log.d("CalendarModule", "Creating event " + name + " at " + location);
  }
}

// JavaScript usage
import { NativeModules } from 'react-native';

const { CalendarManager } = NativeModules;

function MyComponent() {
  const createEvent = () => {
    CalendarManager.addEvent('Meeting', 'Office');
  };
  
  return <Button title="Create Event" onPress={createEvent} />;
}

Rarity: Medium Difficulty: Hard


16. How do you handle platform-specific code?

Answer: Multiple approaches for platform-specific code:

  1. Platform module: Check platform at runtime
  2. Platform-specific files: .ios.js and .android.js
  3. Platform.select: Select values based on platform
import { Platform } from 'react-native';

// Platform module
const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.OS === 'ios' ? 20 : 0,
  },
});

// Platform.select
const styles = StyleSheet.create({
  container: {
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.3,
      },
      android: {
        elevation: 4,
      },
    }),
  },
});

// Platform-specific files
// Button.ios.js
export default function Button() {
  return <Text>iOS Button</Text>;
}

// Button.android.js
export default function Button() {
  return <Text>Android Button</Text>;
}

// Usage - automatically picks correct file
import Button from './Button';

// Platform version check
if (Platform.Version >= 21) {
  // Android API 21+
}

Rarity: Very Common Difficulty: Easy


17. What is the New Architecture (Fabric and TurboModules)?

Answer: The New Architecture improves React Native performance:

  • Fabric: New rendering system
    • Synchronous layout
    • Better interop with native views
    • Type safety
  • TurboModules: New native module system
    • Lazy loading
    • JSI (JavaScript Interface) - direct C++ communication
    • No bridge serialization

Benefits:

  • Faster startup
  • Lower memory usage
  • Synchronous native calls
  • Better type safety

Rarity: Medium Difficulty: Hard


18. How do you handle deep linking in React Native?

Answer: Deep linking allows opening specific screens from URLs.

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

// Configure deep linking
const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Home: 'home',
      Profile: 'profile/:id',
      Settings: 'settings',
    },
  },
};

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

// Handle incoming links
useEffect(() => {
  const handleUrl = ({ url }) => {
    console.log('Opened with URL:', url);
    // myapp://profile/123
  };
  
  // App opened from link
  Linking.getInitialURL().then(url => {
    if (url) handleUrl({ url });
  });
  
  // App already open, new link
  const subscription = Linking.addEventListener('url', handleUrl);
  
  return () => subscription.remove();
}, []);

// Open URL programmatically
const openURL = async (url) => {
  const supported = await Linking.canOpenURL(url);
  if (supported) {
    await Linking.openURL(url);
  }
};

Rarity: Common Difficulty: Medium


Testing (3 Questions)

19. How do you test React Native components?

Answer: Use testing libraries like Jest and React Native Testing Library.

import { render, fireEvent, waitFor } from '@testing-library/react-native';
import Counter from './Counter';

describe('Counter', () => {
  it('renders initial count', () => {
    const { getByText } = render(<Counter />);
    expect(getByText('Count: 0')).toBeTruthy();
  });
  
  it('increments count when button pressed', () => {
    const { getByText, getByTestId } = render(<Counter />);
    const button = getByTestId('increment-button');
    
    fireEvent.press(button);
    
    expect(getByText('Count: 1')).toBeTruthy();
  });
  
  it('fetches user data', async () => {
    const mockFetch = jest.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve({ name: 'John' }),
      })
    );
    global.fetch = mockFetch;
    
    const { getByText } = render(<UserProfile userId={1} />);
    
    await waitFor(() => {
      expect(getByText('John')).toBeTruthy();
    });
    
    expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/users/1');
  });
});

// Component with testID
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <View>
      <Text>Count: {count}</Text>
      <Button
        testID="increment-button"
        title="Increment"
        onPress={() => setCount(c => c + 1)}
      />
    </View>
  );
}

Rarity: Common Difficulty: Medium


20. How do you test Redux logic?

Answer: Test reducers, actions, and connected components separately.

import todosReducer, { addTodo, toggleTodo } from './todosSlice';

describe('todos reducer', () => {
  it('should handle initial state', () => {
    expect(todosReducer(undefined, { type: 'unknown' })).toEqual([]);
  });
  
  it('should handle addTodo', () => {
    const actual = todosReducer([], addTodo('Learn Redux'));
    expect(actual[0].text).toEqual('Learn Redux');
    expect(actual[0].completed).toEqual(false);
  });
  
  it('should handle toggleTodo', () => {
    const previousState = [{ id: 1, text: 'Test', completed: false }];
    const actual = todosReducer(previousState, toggleTodo(1));
    expect(actual[0].completed).toEqual(true);
  });
});

// Testing connected component
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';

function renderWithRedux(component, initialState) {
  const store = configureStore({
    reducer: { todos: todosReducer },
    preloadedState: initialState,
  });
  
  return {
    ...render(<Provider store={store}>{component}</Provider>),
    store,
  };
}

test('displays todos from store', () => {
  const { getByText } = renderWithRedux(<TodoList />, {
    todos: [{ id: 1, text: 'Test Todo', completed: false }],
  });
  
  expect(getByText('Test Todo')).toBeTruthy();
});

Rarity: Common Difficulty: Medium


21. What is E2E testing and which tools do you use?

Answer: End-to-end testing simulates real user interactions.

  • Tools:
    • Detox: Popular for React Native
    • Appium: Cross-platform
    • Maestro: Newer, simpler
// Detox test
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });
  
  it('should login successfully', async () => {
    await element(by.id('email-input')).typeText('user@example.com');
    await element(by.id('password-input')).typeText('password123');
    await element(by.id('login-button')).tap();
    
    await waitFor(element(by.text('Welcome')))
      .toBeVisible()
      .withTimeout(5000);
  });
  
  it('should show error for invalid credentials', async () => {
    await element(by.id('email-input')).typeText('wrong@example.com');
    await element(by.id('password-input')).typeText('wrong');
    await element(by.id('login-button')).tap();
    
    await expect(element(by.text('Invalid credentials'))).toBeVisible();
  });
});

Rarity: Medium Difficulty: Medium


Related Posts

Recent Posts

Weekly career tips that actually work

Get the latest insights delivered straight to your inbox