Skip to content
Web and Mobile Development

React Native interview questions and answers

54 questions, 13 with code. About 3 h 46 min to read them all.

By TestLab · Updated

Plan for an interview
54 questions

Track what you know and what to revise.

Sign in to track progress

You can open a few full answers without signing in. Sign in to read all

1

Could you define React Native and highlight the primary differences between it and React when building user interfaces?

React Native is an open-source framework developed by Meta that enables building native mobile applications for iOS and Android using React principles. Unlike React for web, which renders to the Document Object Model (DOM), React Native compiles JavaScript to native UI components, allowing for platform-specific performance and look-and-feel while sharing business logic.

Read full answer

Applies toReact Native 0.73+ with New Architecture and Hermes

What Is React Native?

React Native is a cross-platform mobile development framework that uses React to build truly native mobile applications. Developers write application logic in JavaScript or TypeScript, and React Native translates those elements into native platform views. The framework shares concepts with React for web—such as declarative UI, component-based architecture, and state management—but targets mobile platforms instead of browsers.

Core Differences Between React and React Native

Feature React (Web) React Native (Mobile)
Rendering Target DOM in browser Native UI components (UIView, android.view)
Core Elements HTML tags (<div>, <span>, etc.) Framework components (<View>, <Text>, <Image>)
Styling Approach CSS stylesheets StyleSheet.create with JS objects
Layout System Flexbox (default: row) Flexbox (default: flexDirection: 'column')
Interactions Mouse/keyboard events (onClick, onHover) Touch events (onPress, onLongPress)
Platform Access Browser APIs Native device APIs via bridges/modules
State Persistence localStorage/sessionStorage AsyncStorage (community package)
Network Status Detection navigator.onLine @react-native-community/netinfo

Architecture and Performance

React for the web manipulates the DOM within a browser environment, using the virtual DOM to optimize updates. React Native historically used a “bridge” pattern to asynchronously communicate between JavaScript and native code, but recent releases introduce the New Architecture. With the New Architecture enabled by default, Fabric allows synchronous UI rendering and TurboModules provide more efficient native module invocation, significantly reducing bridge overhead. Hermes, Facebook’s lightweight JavaScript engine optimized for mobile, is the default engine in React Native applications, improving startup time and memory usage compared to JavaScriptCore.

Component Ecosystem and API Changes

Several APIs that were previously included in React Native core have been moved to community packages to allow faster iteration: - Persistent storage: Use @react-native-async-storage/async-storage instead of the deprecated AsyncStorage - Network status: Use @react-native-community/netinfo for connection monitoring - Additional modules are also available through the community ecosystem

Best Practices and Recommendations

For new projects, Expo is recommended as it simplifies configuration, builds, and updates. Follow these guidelines: - Use FlatList or SectionList for scrolling content rather than ScrollView to ensure memory efficiency with long lists - When targeting both platforms, check Platform.OS to handle iOS and Android differences explicitly, such as back button handling on Android or status bar styling - Adopt New Architecture features like Fabric and TurboModules for improved performance - Ensure native dependencies are compatible with Hermes and the target API levels (iOS 12+, Android API 21+)

React Native continues to evolve toward a smoother developer experience while maintaining its value proposition: writing application logic once in JavaScript/TypeScript while achieving near-native performance and platform consistency.

2

How would you describe the "Learn once, write anywhere" philosophy specifically regarding React Native development?

The Learn once, write anywhere philosophy in React Native means developers apply one consistent set of skills—React, JavaScript or TypeScript, hooks, and component architecture—across multiple platforms while accepting that native rendering and platform behavior often require platform-specific adjustments. It emphasizes shared knowledge and reusable logic rather than identical binaries or pixel-perfect identical UI on every platform.

Read full answer

Applies toReact Native 0.73+ with New Architecture and Hermes (Expo recommended for new projects)

Core Philosophy

Coined by Facebook, this slogan distinguishes React Native from traditional “write once, run anywhere” cross-platform tools. The programming language and component model stay consistent, while the underlying native rendering engines remain platform-native. Developers learn React patterns once—state management, component composition, hooks, and declarative UI—and reuse them for iOS, Android, and, with the right targets, web or desktop. Expecting zero platform-specific code, however, is unrealistic.

Sharing Logic vs. UI

Business logic, data fetching, validation, and state management are highly portable. A custom hook that fetches and caches API data behaves the same on iOS and Android. UI components often diverge because of platform design conventions and native behavior differences. - Shared code: Redux or Zustand stores, utility functions, API clients, authentication flows, and most custom hooks. - Platform code: navigation headers, status bar handling, back-button behavior, gestures, and platform-specific animations. - Expo recommendation: For new projects, Expo provides a unified API layer that abstracts many native differences, reducing the amount of custom native code developers must write.

Handling Platform Differences

When divergence is necessary, React Native offers several mechanisms. The Platform module supports runtime checks, while platform file extensions support build-time selection. With the New Architecture and JSI, native modules are more performant and easier to share through code generation, but platform-specific implementations still exist. - Runtime check: Use Platform.OS to conditionally render components or choose values. - File extensions: Name files Component.ios.tsx and Component.android.tsx so the bundler selects the correct implementation. - Native modules: For hardware features such as Bluetooth or camera access, prefer maintained community packages or Expo modules rather than writing separate bridges by hand.

Best Practices and Pitfalls

  • Avoid over-abstraction: Do not force identical UI on both platforms; users expect native look and feel aligned with Material Design on Android and Human Interface Guidelines on iOS.

  • Test early and often: Run code on both simulators and physical devices regularly, because styles and layout can render differently.

  • Adopt the New Architecture: When writing native modules, prefer TurboModules and Fabric components for better type safety, performance, and cross-platform consistency.

  • Use community packages for removed core modules: Modules such as AsyncStorage are no longer in core; install @react-native-async-storage/async-storage for consistent behavior across iOS and Android.

  • Keep platform files small: Put only the genuinely platform-specific parts in .ios or .android files, and share the remainder through common components and hooks.

3

How would you describe the role and structure of components within a React Native application?

Components are the fundamental building blocks of React Native UI and logic. They encapsulate structure, behavior, and styling into reusable units that render to native views on iOS and Android. Unlike web HTML tags, React Native components compile to native widgets, providing near-native performance while maintaining a declarative JavaScript interface.

Read full answer

Applies toReact Native 0.78+ (New Architecture, Hermes)

Core Concept and Composition

React Native follows a component-based architecture where the UI is a tree of components. Each component accepts inputs called props and returns a description of what should appear on the screen. Composition allows developers to build complex screens by combining simple components. For instance, a UserProfile screen might compose a Header, Avatar, and Bio component. This modularity promotes code reuse and simplifies testing. With the New Architecture enabled by default, components leverage Fabric for rendering and TurboModules for native interaction, improving type safety and initialization speed, while the developer-facing API remains largely consistent.

Native versus Composite Components

Components fall into two categories. Native components are primitives provided by the framework that map directly to platform views, such as View (equivalent to UIView on iOS and android.view.View on Android), Text, and Image. Composite components are JavaScript functions that compose native components together. When building custom UI, you typically create composite components using native primitives. Note that modules previously bundled in core, such as AsyncStorage and NetInfo, are now maintained separately and must be installed from community packages like @react-native-async-storage/async-storage and @react-native-community/net-info.

Function Components and Hooks

Modern React Native development favors function components over class components. Function components are plain JavaScript functions that return JSX and manage state and side effects using Hooks. The useState Hook handles local state, while useEffect manages side effects such as subscriptions or network calls. This approach reduces boilerplate and aligns with concurrent rendering features supported by the New Architecture. Class components are still supported but are considered legacy and should not be used for new development.

Platform Differences

While React Native aims for write-once-run-anywhere, platform nuances exist. You can detect the OS using Platform.OS from the react-native package. Styles may differ; for example, shadows on iOS use props like shadowColor, shadowOffset, shadowOpacity, and shadowRadius, whereas Android relies on elevation. Navigation patterns also diverge, such as the hardware back button on Android versus gesture navigation on iOS. When targeting specific behaviors, conditional rendering based on Platform.OS ensures the correct native experience.

Best Practices and Pitfalls

  • Use FlatList: For long lists of data, always use FlatList or SectionList instead of ScrollView to ensure memory efficiency and smooth scrolling.

  • Adopt Expo: For new projects, use Expo (managed workflow) to simplify configuration, builds, and over-the-air updates.

  • Separate Concerns: Keep presentational components separate from logic containers to improve maintainability.

  • Avoid Core Imports for Removed Modules: Check community packages instead of assuming legacy APIs exist in react-native.

  • Test Both Platforms: Test on both iOS and Android simulators or devices, because the Yoga layout engine may interpret styles slightly differently across platforms.

4

Describe the responsibility of the render() method in constructing the user interface of a React Native component.

The render() method is responsible for describing the UI of a class component by returning a JSX element tree derived from the current props and state; in modern React Native, this responsibility is carried by the body of a function component. It must remain pure, computing the UI representation without causing side effects.

Read full answer

Applies toReact Native (current stable release) with New Architecture and Hermes

Historical Context: Class Components

In legacy class components extending React.Component, render() is the only required method. It returns a React element tree that describes what should appear on the screen. React invokes this method when props or state change, then reconciles the new output against the previous one to update native views. Because it can run frequently, it must not contain subscriptions, timers, network requests, or imperative view mutations.

Modern Equivalent: Function Components

Current best practices favor function components with hooks. There is no explicit render() method; the entire component body serves as the render logic. Props arrive as function arguments, state is managed with useState, and side effects are isolated with useEffect. This pattern is the recommended approach for new code and works well with React’s concurrent rendering features and the Hermes engine.

Key Differences

  • Syntax: Class components read this.props and this.state; function components use destructured arguments and hook values.

  • Lifecycle: In classes, render() is separate from lifecycle methods such as componentDidMount; in functions, render logic and effects are colocated in the body and hooks.

  • Performance and architecture: Function components align better with concurrent rendering and are the standard under the New Architecture, while class components are retained mainly for backward compatibility.

Best Practices and Migration

  • Adopt function components: Write all new screens and UI elements as function components using TypeScript.

  • Keep render logic pure: Never perform API calls, subscribe to events, or mutate native views during rendering, whether in render() or a function body.

  • Use Expo for new projects: Initialize with Expo CLI to simplify configuration and stay aligned with current React Native features.

  • Render lists efficiently: Use FlatList or SectionList from react-native for long lists; avoid mapping components inside a ScrollView.

  • Use community packages for removed core modules: For example, install @react-native-async-storage/async-storage for AsyncStorage rather than expecting it in the core.

5

How would you define JSX and describe its role when building interfaces with React Native?

JSX (JavaScript XML) is a syntax extension for JavaScript that lets developers write HTML-like markup inside JavaScript. In React Native, JSX is the declarative syntax used to describe mobile UI structure; it is compiled into JavaScript calls that React turns into native views on iOS and Android. It is not HTML and is not rendered directly by a browser.

Read full answer

Applies toReact Native 0.76+ (New Architecture enabled by default, Hermes)

How JSX Is Transformed

Metro and Babel convert JSX during bundling into ordinary JavaScript function calls. With React 18 in current React Native, this normally uses the modern JSX runtime (jsx/jsxs) rather than only the classic React.createElement pattern. Each tag becomes a React element object containing its type, props, and children. React then reconciles that tree against the previous render, and under the New Architecture Fabric passes the changes to native platform views. Hermes executes the generated JavaScript more efficiently, but it does not alter JSX syntax or transformation rules.

Syntax Rules and Constraints

  • Single root element: a component must return one parent node; use <>...</> Fragment or a View when siblings are needed.

  • JavaScript expressions: dynamic values go inside {}. Variables, arithmetic, ternaries, and function calls are allowed; statements such as if or for are not.

  • Props naming: use camelCase (numberOfColumns, accessibilityLabel), not HTML-style kebab-case.

  • Self-closing tags: elements without children must be written as <Image />.

  • Capitalization: an uppercase tag (<UserProfile>) is treated as a custom React component. Lowercase tags are treated as host primitives, but React Native’s core primitives (View, Text, Pressable) are capitalized, so lowercase tags are generally invalid.

Role in React Native

JSX separates what the UI should look like from how state changes are computed. Because it is just JavaScript, developers can map arrays to components, conditionally include elements with && or ternaries, and spread props. In React Native, JSX tags map to native components rather than HTML: <View> becomes UIView on iOS and a ViewGroup subclass on Android, and text must always be wrapped in <Text>. This keeps the UI definition close to the logic that drives it while still allowing React Native to produce platform-specific native views.

JSX Compared with Web React

Aspect React Native JSX React for Web JSX
Target elements Native components (View, Text, Pressable) HTML elements (div, span, button)
Text rendering Requires <Text> wrapper Renders directly inside elements
Styling StyleSheet.create or style objects CSS classes, modules, or inline styles
Common events onPress, onScroll, onChange onClick, onScroll, onChange
Layout model Flexbox by default, platform-aware CSS box model, Flexbox optional

Best Practices and Common Pitfalls

  • Project setup: initialize new projects with Expo (npx create-expo-app) to receive current templates and New Architecture defaults.

  • Styles: prefer StyleSheet.create over repeatedly creating inline style objects; it improves readability and enables style reuse.

  • Lists: render long datasets with FlatList or SectionList, not by mapping arrays directly, and always supply a stable key.

  • Logic separation: move complex calculations or conditional UI out of the JSX return; use variables or small helper components.

  • Accessibility: add accessibilityLabel, accessibilityRole, and testID where appropriate so screen readers and automation can identify controls.

  • Fragments: prefer <> over extra View wrappers to avoid unnecessary nesting in the native view hierarchy.

  • JSX runtime: with the modern runtime, import React from 'react' is no longer needed solely to use JSX, although React itself is still required for hooks and component APIs.

6

What are the main built-in UI elements available in React Native for building interfaces?

React Native exposes a set of fundamental core components that render to native widgets rather than HTML, giving mobile interfaces built-in access to platform views for layout, text, images, input, lists, and touch feedback. These components ship with the react-native package and form the standard UI layer for both iOS and Android.

Read full answer

Applies toReact Native 0.75+ (current stable series) with New Architecture enabled by default and Hermes; Expo is recommended for new projects.

Core UI Components

  • View: The layout container; maps to UIView on iOS and android.view.View on Android, and uses Yoga flexbox for layout.

  • Text: Renders native text; maps to UILabel on iOS and TextView on Android; child text inherits styles and strings must be wrapped in <Text>.

  • Image: Displays network URIs, local assets, or static image resources; requires a source prop and supports PNG, JPEG, WebP, and GIF with platform-dependent codec support.

  • ScrollView: A scrollable wrapper backed by UIScrollView on iOS and android.widget.ScrollView on Android; suitable for small, fixed content but renders all children at once.

  • FlatList: The performant list component for long or dynamic data; recycles off-screen rows and should be used instead of ScrollView for lists.

  • TextInput: A native text field that supports keyboardType, multiline, auto-capitalization, and secureTextEntry for passwords.

  • Pressable: The recommended built-in touch responder; exposes press state such as pressed and hovered and replaces older opacity wrappers for most cases.

  • TouchableOpacity: Still available and reduces opacity on press, but Pressable is preferred for finer control.

  • ActivityIndicator: Shows a native spinning indicator; controlled by animating, and platform color defaults differ.

  • Switch: A toggle control whose visual style follows iOS or Android conventions.

  • SafeAreaView: Insets content away from notches and the home indicator on iOS; has limited effect on Android unless the app uses edge-to-edge drawing.

  • KeyboardAvoidingView: Moves or resizes content when the on-screen keyboard appears; its behavior and required behavior value differ by platform.

  • Modal: Presents content full-screen; implemented as a native modal on each platform.

Platform Differences

Component iOS Android
TextInput Renders UITextField; default keyboard style matches iOS Renders EditText; Material styling by default
Switch iOS-style slider track Material toggle
SafeAreaView Required for notch and home-indicator avoidance Only relevant with edge-to-edge UI
ActivityIndicator Gray spinner by default Follows Material color conventions
KeyboardAvoidingView behavior="padding" or "height" works reliably Often needs behavior="position" and manual tuning
ScrollView / FlatList pagingEnabled works natively May need windowSoftInputMode configured in AndroidManifest.xml to keep inputs visible

Choosing the Right Component

  • Use FlatList whenever the list is longer than a screen or data is fetched incrementally; use ScrollView only for short, static forms or content.

  • Prefer Pressable over TouchableOpacity when you need hover, pressed, or focus feedback; keep TouchableOpacity only for simple legacy opacity effects.

  • Wrap screens in SafeAreaView on iOS and test KeyboardAvoidingView on both platforms before shipping forms.

  • Use TextInput with secureTextEntry={true} for passwords and credit-card fields; there is no separate SecureEntry component.

Best Practices and Common Pitfalls

  • Create styles with StyleSheet.create() rather than inline objects so they can be hoisted and reused.

  • Supply a stable keyExtractor to FlatList instead of relying on default indices.

  • Omitting source on Image renders an empty view and warns, rather than crashing.

  • Do not assume identical metrics: font rendering, touch hit slops, and default colors vary between iOS and Android.

  • Avoid nesting many View layers unnecessarily; prefer flexbox layout and platform-specific sheets only when needed.

7

Define props in React Native and explain their role in component communication.

Props are read-only attributes passed from a parent component to a child component, enabling data flow and customization of UI elements in React Native applications.

Read full answer

Applies toReact Native 0.73+ with New Architecture and Hermes, all versions support basic props

What Are Props?

Props (short for properties) serve as the primary mechanism for passing data between components in React Native. They are immutable from the perspective of the receiving component and follow unidirectional data flow patterns essential to React’s design philosophy. Data flows downward from parent to child, never upward without callback functions.

Key Characteristics

  • Immutable: Props cannot be modified by the receiving component; attempting mutation causes runtime invariant violations

  • Parent-to-child only: Data flows downward from parent to child, never upward without callbacks

  • Optional by nature: Children can define default parameter values when props might not be provided

  • Flexible types: Props can hold primitives, strings, numbers, booleans, objects, arrays, functions, or even other components

  • Type safety: TypeScript interfaces can define expected prop structures for static type checking

Common Prop Patterns

  • Data Props: Passing information like text content, images, or configuration details to display or process

  • Callback Props: Functions passed down that children invoke to communicate events back up to parents

  • Component Props: Nested components rendered conditionally or dynamically within children

  • Style Props: View-specific styling through inline styles or StyleSheet references

Props vs State

Aspect Props State
Origin External (parent component) Internal (own component)
Mutability Read-only Mutable via setState or update function
Scope Shared between components Private to individual component
Lifecycle Changes trigger re-render if parent re-renders Changes trigger immediate re-render

Best Practices

  1. Use TypeScript for validation: Define prop interfaces to catch errors at development time rather than runtime.

  2. Name callbacks meaningfully: Callback props should indicate intent (e.g., onSubmit, onPress rather than just action).

  3. Provide sensible defaults: For optional props, use default parameter values instead of conditional rendering for simple cases.

  4. Avoid prop drilling: When deeply nested components need the same data, consider Context API or state management libraries.

  5. Don’t mutate props: Creating local copies before modifications prevents accidental side effects and invariant violations.

Platform-Specific Considerations

Both iOS and Android share identical prop behavior in React Native. Component-specific props may differ slightly—for example, some native components may require platform-specific props like numberOfLines on Text components or accessibility props that behave differently across platforms, but these are component-level differences, not prop system differences.

Common Pitfalls

  • Attempting to modify props directly causes runtime invariant violations in development mode

  • Missing required callback props leads to runtime crashes when invoked

  • Not using keys properly with list items causes performance degradation

  • Overloading component props with too many responsibilities reduces reusability

8

How is the useState hook utilized in React Native applications?

The useState hook enables functional components in React Native to manage local component state without converting to class components. This foundational React feature allows dynamic UI updates when data changes within a screen or component.

Read full answer

Applies toReact Native 0.74+ with New Architecture and Hermes engine

How It Works

useState returns two values: the current state value and a function to update it. When the update function is called, React schedules a re-render of the component with the new state value. In React Native, each state change triggers updates across both Android and iOS platforms simultaneously since they share the same JavaScript bundle.

Key Features

  • Returns a tuple with current state value and setter function via array destructuring

  • State persists as long as the component stays mounted

  • Triggers re-renders only for the affected component tree

  • Accepts any JavaScript type including primitives, objects, and arrays

  • Maintains separate state instances for each component using the hook

Usage Patterns

  • Text input: Store typed characters and update display dynamically

  • Form fields: Manage multiple input states with individual hooks or combined objects

  • Toggle behaviors: Switch boolean states for UI toggles and switches

  • Selection lists: Track selected items in pickers and list selections

  • Loading states: Control visibility of loading spinners and progress indicators

Differences Between Android and iOS

React Native treats both platforms identically with useState since state management happens in the JavaScript thread. However, native animations triggered by state changes may perform differently due to platform-specific render engines. The New Architecture improves performance consistency across Android and iOS through direct mode rendering.

Component Isolation

Each component maintains independent state instances. Reusing the same code doesn’t cause state sharing issues. For example, ten FlatList items each have their own checkbox state when useState manages selection.

State Update Behavior

State updates are asynchronous and batched for performance. Multiple calls within an event handler merge into one render cycle. Don’t rely on immediate state reflection after calling the setter—check state from closures instead.

Advantages and Limitations

Advantages Limitations
Simple syntax for basic state needs Not ideal for complex state logic across components
Works with Expo SDK without native setup Requires careful dependency management in useEffect
Compatible with TypeScript interfaces State reset requires explicit setter call
Enables declarative UI programming Cannot be used in class components

Common Pitfalls

  • Omitting dependency array causes stale closures in callbacks

  • Using previous state value directly instead of functional updates

  • Forgetting to check if component unmounts before setting state

  • Mutating state objects instead of creating new references

  • Updating state without triggering necessary re-renders

Best Practices

  • Use TypeScript interfaces to define state types

  • Combine related state into single object for better management

  • Use functional state updates when new value depends on old value

  • Keep state as close as possible to where it’s consumed

  • Consider useReducer for complex state logic scenarios

  • Clear state when navigating away from screens

Code Example

import React, { useState } from 'react';
import { View, TextInput, Button, Text } from 'react-native';

interface LoginFormData {
  email: string;
  password: string;
}

const LoginScreen = () => {
  // Declare state variables with initial empty strings
  const [formData, setFormData] = useState<LoginFormData>({
    email: '',
    password: '',
  });

  // Single piece of state for loading indicator
  const [isLoading, setIsLoading] = useState<boolean>(false);

  const handleEmailChange = (text: string) => {
    // Update only email field while preserving password
    setFormData((prev) => ({ ...prev, email: text }));
  };

  const handleSubmit = async () => {
    setIsLoading(true);
    try {
      // Call API with validated parameters
      await fetch('https://api.example.com/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          email: formData.email.trim(),
          password: formData.password
        })
      });
    } catch (error) {
      console.error('Login failed:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <View style={{ padding: 20 }}>
      <TextInput
        placeholder="Email"
        value={formData.email}
        onChangeText={handleEmailChange}
        autoCapitalize="none"
        keyboardType="email-address"
        autoComplete="email"
      />

      <TextInput
        placeholder="Password"
        value={formData.password}
        onChangeText={(text) => 
          setFormData((prev) => ({ ...prev, password: text }))
        }
        secureTextEntry
        autoComplete="password"
      />

      <Button 
        title={isLoading ? "Logging in..." : "Login"} 
        onPress={handleSubmit}
        disabled={isLoading}
      />
    </View>
  );
};

export default LoginScreen;

This example demonstrates proper state initialization, TypeScript typing, functional updates, and async handling with loading states suitable for production apps.

10

In React Native component development, how do state and props differ regarding data ownership and mutability?

In React Native, props are immutable inputs passed from parent to child, while state is mutable data managed internally within a component. This ownership difference determines where data can change and enforces a predictable unidirectional flow through the component tree.

Read full answer

Applies toReact Native 0.74+ (New Architecture enabled by default), Hermes, Expo SDK 51+

Core Definitions

  • Props (Properties): Configuration values supplied by a parent component, analogous to function arguments. They are read-only from the receiver’s perspective and describe how a component should render for a given input.

  • State: Local data owned by a component that can change over time in response to events, network responses, or timers.

  • Ownership: The component that creates a piece of data owns it. If the data must change, the change must happen at its owner, preserving a single source of truth.

Aspect Props State
Source Passed from a parent component Created and held inside the component
Mutability Read-only; must never be mutated by the receiver Mutable only through its setter (useState/useReducer)
Ownership Belongs to the parent Belongs to the component itself
Purpose Configure rendering and behavior Track values that change over time
Update trigger Parent re-renders and passes new values State setter is called

Data Flow and Mutability

React Native follows strict unidirectional data flow: data moves down the tree via props. If a child needs to affect data owned by its parent, it calls a callback function passed down as a prop rather than writing to the parent’s data directly. - Immutability of props: A component must never reassign or mutate its own props. Doing so breaks the React reconciliation contract and produces undefined rendering behavior. - Mutability of state: State changes only through the setter returned by useState (or useReducer). Direct assignment mutates the variable without scheduling a re-render, so the UI stays stale. - Re-renders: A state update schedules a re-render of the owning component and its children. A child also re-renders when its parent renders, receiving fresh props if the parent supplied new values.

Usage with Function Components and Hooks

Modern React Native development favors function components with hooks; class components remain supported but are discouraged. Expo is recommended for new projects, though the state/props model is identical in bare React Native. - Initialization: State is initialized once during mount. Initial values may be derived from props, but later prop changes do not automatically update state unless an effect explicitly synchronizes them. - Updates: Calling a state setter schedules a re-render. React 18’s automatic batching groups multiple state updates inside event handlers for better performance. - Derived data: Do not store values in state if they can be computed from props or other state. Computed values keep render logic pure and avoid synchronization bugs.

Common Pitfalls

  • Mutating props: Never reassign props inside the component body. Treat them as constant inputs for each render cycle.

  • Prop drilling: Passing props through many intermediate layers can be reduced with the Context API or libraries such as Zustand or Jotai.

  • Stale closures: When state depends on previous state, use the functional update form (setCount(prev => prev + 1)) to avoid reading stale values in asynchronous handlers.

  • Unnecessary state: Avoid duplicating data in state when it is already available from props or can be derived; divergent sources cause inconsistent UI.

Platform Considerations

The semantics of state and props are identical on Android and iOS because they are governed by the React reconciliation engine, not native platform behavior. However, the cost of re-renders can differ because the underlying native view hierarchies vary. Heavy computations derived from state should be memoized with useMemo to prevent UI jank, especially on lower-end Android devices.

11

How would you define higher-order components and describe their typical usage within React Native applications?

A higher-order component (HOC) is a function that receives a component and returns a component, allowing reusable behavior such as prop injection, state, rendering control, or effects to be added without changing the original component. In modern React Native, custom hooks are generally preferred for sharing stateful logic, while HOCs remain useful for render-level composition.

Read full answer

Applies toReact Native 0.76 and later with the New Architecture enabled by default and Hermes

Core Concept

A typical HOC follows the shape withFeature(WrappedComponent) and returns a new function component. The returned component can use React hooks, render WrappedComponent with additional props, or choose not to render it at all.

HOCs operate in React Native’s JavaScript layer, before native rendering. The mechanism therefore works the same way on iOS and Android, although a wrapped native component may still have platform-specific props, permissions, or minimum OS requirements. A component such as withAnalytics(Screen) can run an effect when Screen mounts and pass tracking-related props to it.

HOCs Versus Custom Hooks

Aspect Higher-order component Custom hook
Primary use Wraps a component with behavior or props Shares stateful logic inside a component
Component tree Usually adds a wrapper component Does not require an additional component
Best fit Conditional rendering, prop injection, and render-time composition Sharing state, effects, and derived values
Testing Test the exported wrapper and its integration Test the hook through a small host component
Performance Can add render depth Avoids a wrapper when it directly replaces equivalent HOC behavior

Hooks are not automatically faster than HOCs, and an HOC is not inherently less readable. The choice depends on whether the reusable concern is component-level composition or stateful logic.

Common Use Cases

  • Authentication guards: A wrapper can render a loading or redirect state until the session is known, then render the protected screen.

  • Theming: A wrapper can inject theme values from context without changing every consumer.

  • Analytics: A wrapper can use useEffect to report screen views when the wrapped screen mounts.

  • Feature flags: A wrapper can conditionally render a component when its feature is enabled.

  • Error handling: A wrapper can compose an existing class-based error boundary or the third-party react-error-boundary package. React error boundaries remain class components.

Best Practices and Pitfalls

  • Define HOCs outside render functions: Creating a new wrapped component during render changes its identity and can cause unnecessary unmounts and remounts.

  • Define prop precedence explicitly: Document whether wrapper props override or merge with the caller’s props, and remember that composition order changes the result.

  • Forward refs when necessary: Use React.forwardRef when the wrapped component must expose a ref to a native view.

  • Preserve intentional static APIs: If consumers depend on static members, copy them deliberately or use the hoist-non-react-statics package rather than copying every static indiscriminately.

  • Set displayName: It improves component names in React Native DevTools and error stack traces.

  • Prefer hooks for new stateful logic: For new projects, Expo is the recommended starting point; use bare React Native when specific native requirements make it necessary. Choose an HOC when component-level wrapping is the clearer abstraction.

12

What is the proper way to implement and use Context in React Native applications?

Context API is React’s built-in mechanism for sharing data across components without prop drilling, allowing you to pass values and functions through the component tree automatically.

Read full answer

Applies toReact Native 0.73+ (New Architecture enabled by default, Hermes) with TypeScript or JavaScript; Expo is recommended for new projects.

How It Works

  • createContext(defaultValue) returns an object with Provider and Consumer members.

  • The Provider makes the current value available to every descendant in the tree.

  • Consumers read the value either via <Context.Consumer> or, in function components, with the useContext(Context) hook.

  • When the provider’s value changes, all consuming components re-render.

Implementation Steps

  1. Define the context object, ideally with a TypeScript type and an undefined default so a custom hook can detect a missing provider.

  2. Build a provider component that owns the state (usually with useState) and memoizes the value with useMemo.

  3. Export a custom hook that calls useContext and throws when used outside the provider.

  4. Render the provider near the app root (or the subtree that needs the data).

  5. Consume the context in any descendant through the custom hook.

Code Example

import React, { useState, createContext, useContext, useMemo } from 'react';
import { Button, Text, View } from 'react-native';

type Theme = 'light' | 'dark';

interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

// Default is undefined so the custom hook can detect a missing provider.
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light');

  const toggleTheme = () => {
    setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
  };

  // Memoize the value so a new object reference does not cascade re-renders.
  const value = useMemo(() => ({ theme, toggleTheme }), [theme]);

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

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

export function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  // React Native Button uses the title prop, not children.
  return <Button title={theme} onPress={toggleTheme} />;
}

Performance Considerations

Approach When to Use Caveat
Plain context Static config, auth tokens, infrequent updates All consumers re-render on any value change
Context + useReducer Complex state transitions Keeps reducer logic out of components
Multiple narrow contexts Independent domains (theme, auth, settings) Prevents unrelated updates from triggering re-renders

Wrap dynamic values in useMemo so that a fresh object reference does not cascade re-renders.

Best Practices and Common Pitfalls

  • Split contexts by concern: one context per domain rather than a single giant store.

  • Memoize the provider value: useMemo prevents a fresh object on every render.

  • Guard against missing providers: throw in a custom hook instead of silently using undefined.

  • Prefer hooks over <Consumer>: useContext is cleaner in function components.

  • Don’t replace local state: context is for data many components need, not every intermediate value.

  • Platform note: context lives in the JavaScript layer, so behavior is identical on Android and iOS; Hermes does not change it. On low-memory devices avoid storing large binary blobs in context.

13

What alternatives exist for managing global state within a React Native application besides relying on Redux or the Context API?

Several external-store and reactive-state patterns can replace Redux or a broad Context setup. The best choice depends on whether the application is managing shared client state, cached server data, or explicit workflows.

Read full answer

Applies toReact Native 0.76 with the New Architecture enabled by default and Hermes; Zustand 5.x, Jotai 2.x, MobX 6.x with `mobx-react-lite` 4.x, and TanStack Query 5.x

Main Alternatives

Option State model Best suited for Main limitation
Zustand Hook-based external store with selectors Shared application and client state Advanced workflows require deliberate store organization
Jotai Small atoms with derived values Fine-grained state and computed dependencies A large number of interdependent atoms can become difficult to track
MobX Observable values, actions, computed values, and reactions Complex derived state and event-driven updates Introduces more concepts than a minimal store
Custom external store Module-level state plus subscription functions Small, isolated pieces of global state Selectors, immutability, persistence, and testing must be implemented manually
TanStack Query Asynchronous cache with query keys and invalidation Server state such as API results It does not replace local UI or workflow state

How State Propagation Works

  • Zustand exposes a hook backed by an external store. Components subscribe to selected values and re-render when those selections change. It supports middleware for persistence and development tooling without requiring a Context provider.

  • Jotai represents state as atoms. Components subscribe only to atoms they read, while derived atoms calculate values from other atoms. This provides fine-grained subscriptions and lazy computation.

  • MobX makes values observable. Actions modify observables, computed values derive from them, and reactions run when tracked values change. React components use observer from mobx-react-lite to participate in this reactivity.

  • A custom external store can expose getSnapshot and subscribe functions and integrate with React through useSyncExternalStore. This is practical only for small stores; recreating a mature state library usually adds risk without meaningful benefit.

Server and Workflow State

Global state should not be treated as one undifferentiated bucket. TanStack Query is a strong alternative when the main problem is keeping remote data synchronized, because it handles caching, retries, stale data, and invalidation. Its QueryClientProvider is still a root provider, but the provider manages a server-state cache rather than replacing every application state pattern.

For multi-step processes such as checkout or onboarding, a state-machine approach can make valid states and transitions explicit. This is particularly useful when the application must prevent invalid transitions or recover from interruptions.

Choosing an Option

  • Use Zustand for a small-to-medium collection of shared client-side values, especially when selectors and middleware are important.

  • Use Jotai when state naturally decomposes into atoms or when derived values are central to the design.

  • Use MobX when actions, computed values, and reactions are fundamental to the application.

  • Use TanStack Query for remote data and a client-state library for local state when both are needed.

  • Use a custom external store only for a small, stable domain where avoiding a dependency is more important than missing library features.

Best Practices and Platform Notes

  • Keep UI-only state local and reserve global state for information genuinely shared across distant screens.

  • Use narrow selectors and stable selector outputs. For object-valued Zustand selections, use an equality mechanism such as useShallow when appropriate.

  • Keep remote data out of manually synchronized global stores. Let TanStack Query own server cache invalidation and background refresh behavior.

  • Treat singleton stores as shared test fixtures. Expose reset actions or create stores through factories so tests do not leak state between cases.

  • For small durable preferences, @react-native-async-storage/async-storage can be used with Zustand’s persistence middleware. Do not store authentication secrets or other sensitive credentials there; use OS-backed secure storage instead.

  • These client-state libraries execute in JavaScript on Hermes and behave consistently on Android and iOS. Persistence is subject to each operating system’s app-data, uninstall, and backup policies, so it should not be treated as guaranteed durable storage.

14

What are the key strategies and techniques for improving performance in a React Native application?

Optimizing a React Native application centers on running the Hermes JavaScript engine, adopting the New Architecture, and minimizing unnecessary renders and JavaScript-thread work. Together with efficient list rendering, asset handling, and disciplined profiling, these strategies produce fast startup and smooth 60 fps interactions on Android and iOS.

Read full answer

Applies toReact Native 0.74+ (New Architecture enabled by default), Expo SDK 51+

Engine and Architecture

  • Hermes: compiles JavaScript to bytecode ahead of time, shrinking bundle size and improving Time To Interactive. It is enabled by default in React Native 0.74+ and Expo, so only verify the Hermes setting in android/app/build.gradle and ios/Podfile if the project has been customized.

  • Fabric and TurboModules: the New Architecture uses the JavaScript Interface (JSI) to let the UI thread update synchronously and native modules be invoked with far less bridge overhead than the legacy async bridge.

  • Platform behavior: Hermes gives the largest memory reduction on Android; on iOS its main benefit is faster launch and a smaller memory footprint.

Rendering Optimization

  • Function components and hooks: prefer them over class components because they work cleanly with memoization and the New Architecture.

  • React.memo: wrap pure presentational components so they skip re-render when props are shallowly equal.

  • useCallback and useMemo: stabilize function and object props; inline functions or objects create new references every render and defeat memoization.

  • Dependency arrays: keep them correct to avoid stale closures, missed effects, or infinite loops.

  • State scope: keep state as local as possible and lift it only when needed, so changes do not propagate through unrelated parts of the tree.

List Rendering

  • FlatList or SectionList: use these for long or dynamic lists because they render only visible items; ScrollView renders every child and should be reserved for short, static content.

  • initialNumToRender: set a small number so the first screen appears immediately.

  • windowSize: control how many screen-lengths are buffered ahead and behind the visible area.

  • getItemLayout: supply it when item heights are constant to skip per-item measurement during scrolling.

  • extraData: pass it only when truly required, because it forces every visible item to re-render.

Component Best for Main risk
ScrollView Short, static lists or forms Renders all children, hurting memory and mount time
FlatList Long, dynamic, or infinite lists Requires stable keys and a memoized renderItem to avoid wasted renders

Images and Assets

  • Correct resolution: request images sized for the device pixel ratio to avoid decoding oversized bitmaps.

  • Caching: use expo-image in Expo projects or equivalent community caching libraries to prevent repeated network fetches.

  • Platform differences: optimize PNGs on Android; prefer vector assets on iOS where possible to reduce bundle size.

  • Server-side resizing: resize large images on the server before sending them to the device.

Best Practices and Pitfalls

  • Profile first: use React Developer Tools or Flipper to find real bottlenecks before optimizing.

  • Persistent data: store non-volatile data in @react-native-async-storage/async-storage rather than keeping large blobs in global state.

  • Logging: remove console.log calls or guard them with __DEV__, because logging on the JavaScript thread adds overhead in production.

  • Native animations: set useNativeDriver: true on Animated calls when the animation properties allow it, so work stays off the JavaScript thread.

  • Native module calls: batch calls to native modules instead of issuing many small ones.

  • Bundle size: remove unused dependencies and avoid importing entire libraries when only one function is needed.

15

In React Native, what role does the Virtual DOM play, and how does the framework translate React rendering updates into native UI changes?

The Virtual DOM in React Native is an in-memory representation of the component hierarchy used to determine what changed between renders. React Native applies React’s reconciliation model to native UI components rather than HTML DOM elements, then forwards the resulting changes to its Fabric renderer for application on iOS and Android.

Read full answer

Applies toReact Native 0.80 with the New Architecture and Hermes

How It Works

  1. JSX creates React elements: Component functions return JavaScript objects that describe the desired UI hierarchy. These elements are not screen pixels or native views themselves.

  2. React renders components: When props, state, or context change, React executes the relevant component functions and creates a new element tree.

  3. React reconciles the trees: React compares the new tree with the previous tree, preserves components that do not need to change, and identifies the host components whose properties or children must be updated.

  4. Fabric applies native changes: The New Architecture’s Fabric renderer translates those updates into operations for the platform’s native view system:

  5. On iOS, components correspond primarily to UIView subclasses.

  6. On Android, components correspond primarily to Android View subclasses and related native widgets.

  7. The platform renders the result: Each platform renders its native views using its own graphics and layout infrastructure.

A state update therefore does not necessarily rebuild the entire screen. If a state change does not affect the rendered output, React may stop reconciliation without sending a native UI update.

Virtual DOM Versus Native Views

React DOM React Native
Represents browser elements such as <div> and <span> Represents native components such as View and Text
Updates are applied to DOM nodes Updates are applied to native view instances
Browser performs layout and painting The operating system’s UI toolkit performs layout and rendering
Uses the browser’s rendering engine Uses native UIKit on iOS and Android UI rendering on Android

React Native does not create a literal copy of every native view in JavaScript. The virtual representation focuses on component structure, props, children, and state. The native view hierarchy remains managed by the platform renderer.

Role of the New Architecture and Hermes

The New Architecture is enabled by default in React Native 0.80 and changes how rendering and native communication are implemented.

  • Fabric: A C++-based renderer that works with platform-specific view implementations. It maintains renderer-side information about the native component hierarchy and applies mutations efficiently.

  • Hermes: The JavaScript engine used to execute application JavaScript, including React rendering logic. Its startup and runtime characteristics are generally more predictable than the alternative of starting a separate browser-style JavaScript engine.

  • Native modules: The New Architecture supports the modern TurboModule and JSI-based communication model instead of relying on the legacy asynchronous JSON bridge as the central native-communication mechanism.

Fabric changes the native rendering path, but React’s declarative model and reconciliation behavior remain familiar.

Why Reconciliation Matters

Reconciliation allows a component tree to behave like a description of the desired UI. Developers specify relationships such as a parent containing children, but React determines the minimum required native operations.

For example, changing a Text component’s color prop can result in a targeted property update. Adding or removing children may require native view insertion, removal, or reordering. React avoids replacing native components when their identity and relevant properties remain unchanged.

However, reconciliation does not automatically make every application fast:

  • Expensive component rendering still consumes JavaScript-thread time.

  • Large component trees can increase reconciliation and memory costs.

  • Unnecessary state updates can trigger additional renders.

  • Native layout can still be expensive even when the JavaScript update is small.

FlatList and SectionList address a different problem: rendering very long data sets. They virtualize list items and reduce the number of mounted views; the Virtual DOM does not itself provide list virtualization.

Practical Guidance

  • Keep state as local as practical: Local state usually limits the portion of the tree that React must reconsider.

  • Memoize expensive subtrees when justified: React.memo, useMemo, and useCallback can reduce unnecessary work, but they add complexity and should be driven by measurements.

  • Use stable keys for lists: Keys such as item.id help React match items correctly across renders; array indexes can produce incorrect state associations when ordering changes.

  • Avoid assuming every render changes the screen: React may reconcile the result and produce no native mutation.

  • Profile before optimizing: React DevTools and platform profilers can reveal whether a bottleneck is JavaScript rendering, reconciliation, native layout, or list rendering.

  • Treat platform-specific props separately: Props such as shadows, typography behavior, permissions, and accessibility behavior may differ between iOS and Android and should be tested on both platforms.

16

How would you create animations in a React Native application?

Use React Native’s Animated API to animate scalar values and apply them to component properties. For simple transitions, use Animated.timing or Animated.spring; for gesture-driven or highly interactive motion, consider using react-native-reanimated.

Read full answer

Applies toReact Native 0.74+ with Hermes (function components and hooks)

Core Animation APIs

  • Animated.Value: Stores an animatable numeric value, such as opacity, position, scale, or progress value.

  • Animated.timing: Animates toward a target value over a fixed duration.

  • Animated.spring: Produces physics-based animation using stiffness, damping, mass, and related parameters.

  • Animated.sequence: Runs animations one after another.

  • Animated.parallel: Starts animations at the same time.

  • Animated.loop: Repeats an animation, optionally with a delay between iterations.

  • Animated.View: Applies animated styles to a native view.

  • interpolate: Maps an animated value from one numeric range to another.

The core Animated API supports opacity and transform animations, including translateX, translateY, scale, rotate, and rotation-related transforms.

Native Driver Behavior

The native driver is now enabled by default and runs animations independently of the JavaScript thread on both platforms. Layout properties such as width, height, top, or left require JavaScript driver computation, so avoid animating them when possible. Use the JavaScript driver explicitly only when animating layout-related properties.

Platform-specific notes: Built-in Animated API supports Android 4.4+ and iOS 9+, but native driver performance may vary on older devices. The native driver is supported on all modern device versions.

Code Example

import React, { useEffect, useRef } from 'react';
import {
  Animated,
  Easing,
  StyleSheet,
  View,
  Text,
} from 'react-native';

export function FadeAndSlide() {
  const progress = useRef(new Animated.Value(0)).current;
  let animationRef: any;

  useEffect(() => {
    animationRef = Animated.timing(progress, {
      toValue: 1,
      duration: 350,
      easing: Easing.out(Easing.cubic),
    });

    animationRef.start();

    // Stop the animation if the component unmounts.
    return () => {
      animationRef?.stop();
    };
  }, [progress]);

  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.card,
          {
            opacity: progress,
            transform: [
              {
                translateY: progress.interpolate({
                  inputRange: [0, 1],
                  outputRange: [40, 0],
                }),
              },
            ],
          },
        ]}>
        <Text style={styles.title}>Animated content</Text>
      </Animated.View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 24,
  },
  card: {
    width: '100%',
    padding: 24,
    borderRadius: 12,
    backgroundColor: '#2563EB',
  },
  title: {
    color: '#FFFFFF',
    fontSize: 18,
    fontWeight: '600',
    textAlign: 'center',
  },
});

Platform and Framework Notes

  • Built-in Animated API works on Android 4.4+ and iOS 9+, though newer versions offer better performance.

  • Native driver is enabled by default and runs on the native UI thread on both Android and iOS.

  • With the New Architecture enabled, function components and hooks remain the recommended pattern for creating animated values.

  • For New Architecture projects, react-native-reanimated 3.x is recommended for gesture-driven, interruptible, or worklet-based animations.

  • For list-item transitions, render items through FlatList or SectionList; avoid placing long lists inside ScrollView.

Best Practices and Common Pitfalls

  • Prefer native-driver-compatible properties: Animate opacity and transforms instead of left, top, or dimensions.

  • Avoid recreating values unnecessarily: Create Animated.Value instances with useRef rather than on every render.

  • Clean up animations: Stop animations during component unmounting when appropriate.

  • Respect accessibility settings: Check AccessibilityInfo.isReduceMotionEnabled() and reduce or disable nonessential motion when the user requests reduced motion.

  • Do not animate without purpose: Use short, predictable transitions for feedback, hierarchy, and navigation.

  • Avoid layout animations for large datasets: Animate list containers rather than every row, and provide stable item keys.

  • Test on both platforms: Verify interruption behavior, rendering performance, and visual differences on Android and iOS.

17

How does React Native’s useCallback hook work, and what practical benefit does memoizing a callback provide?

useCallback returns a stable function reference between renders when its dependency array does not change. In React Native, this is useful when the callback is passed to a memoized component or affects a prop that should retain its identity, helping avoid unnecessary rerenders.

Read full answer

Applies toReact Native (current stable release with New Architecture enabled by default and Hermes; the hook’s behavior is consistent across all versions)

How It Works

useCallback accepts a callback and a dependency array. The cached callback is reused only if every listed dependency remains unchanged between renders. React returns the same function object while the dependencies are equal, giving reference stability. If a callback reads a changing value, that value must be included in the dependency array; otherwise the callback can retain stale data. Note that useCallback is an optimization, not a cache of results: it does not prevent the component function itself from rerunning, and it does not cache the callback’s return value. The hook operates in JavaScript and has no platform-specific behavior or minimum Android or iOS version requirement.

Why It Matters in React Native

A React Native parent component can rerender because of state, props, context, or an external event. Even if the underlying data has not changed, newly created callback props can cause memoized descendants to rerender because React.memo normally compares props using shallow equality. Common uses include:

  • Memoized child components: Passing a stable callback to a React.memo component prevents that component from rerendering solely because the callback’s identity changed. For example, a row component rendered by FlatList can receive a stable onSelect handler.

  • Stable FlatList props: A memoized renderItem can retain its identity across rerenders. FlatList should still be used for long collections rather than replacing it with ScrollView.

  • Event subscriptions: A stable subscription callback can be used safely as an effect dependency, provided the effect also returns the platform’s unsubscribe function.

  • Navigation and native event handlers: Stable handlers can reduce prop churn when wrapped with React.memo.

useCallback does not automatically prevent a React Native component from rerendering, and it does not necessarily reduce native-side work. The benefit depends on how the callback is consumed.

Best Practices and Common Pitfalls

  • Include every captured value: Treat the dependency array as a contract. Values read by the callback but omitted from the array can produce stale closures.

  • Use it only when identity matters: Wrapping every callback with useCallback adds complexity without guaranteed benefit.

  • Combine it with React.memo: Memoizing a callback is most effective when a memoized child receives that callback as a prop.

  • Keep list data correct: If renderItem depends on changing state, include that state in its dependency array rather than forcing the callback to remain stale.

  • Do not treat it as a performance guarantee: Rendering can still occur because of state changes, context updates, changed props, or parent rerenders.

18

What function does the React Native Bridge perform when exchanging data between JavaScript and native platform code?

The React Native Bridge was the communication layer used by React Native’s legacy architecture to transfer serialized messages between the JavaScript thread and native code on Android and iOS. It is not the primary communication mechanism in the current New Architecture, which uses JSI and native components such as TurboModules and Fabric instead.

Read full answer

Applies toReact Native 0.76 and later with the New Architecture and Hermes; the legacy Bridge applies only when the legacy architecture is enabled.

Historical Role

React Native applications separate much of their execution into two environments:

  • JavaScript thread: Runs application logic, React rendering, and JavaScript-based framework code.

  • Native platform side: Owns Android and iOS UI objects and exposes operating-system capabilities through native modules.

The Bridge connected these environments. It converted JavaScript calls, commands, and events into messages that native code could process. It also delivered native-module results and events back to JavaScript.

For example, a JavaScript function could request a device capability implemented in Kotlin, Java, Objective-C, or Swift. The Bridge carried that request across the language boundary, while native callbacks or events returned through the same communication layer.

How the Legacy Bridge Worked

The legacy Bridge operated through a message-based, asynchronous pipeline:

  • Serialization: Values were converted into a JSON-compatible representation before being sent.

  • Queueing: Messages were scheduled for processing rather than sharing direct object references between JavaScript and native code.

  • Native module calls: JavaScript invoked native modules through NativeModules, and results were normally delivered asynchronously through callbacks or promises.

  • UI operations: The renderer sent commands for creating, updating, measuring, and removing native views.

  • Event delivery: Native events such as button presses or sensor updates were forwarded to JavaScript handlers.

This isolation improved safety and cross-language compatibility, but serialization and queueing introduced overhead. Large payloads or frequent communication could also increase latency and consume CPU on the JavaScript thread.

Comparison With the New Architecture

Aspect Legacy Bridge New Architecture
Communication format Serialized, JSON-compatible messages JavaScript values and native objects accessed through JSI
Typical execution model Mostly asynchronous and queued TurboModule methods can be synchronous or asynchronous
Native modules NativeModules bridge-based modules TurboModules implemented with modern native interfaces
Rendering Bridge-based renderer Fabric renderer
Main benefit Language isolation Lower overhead and direct native integration

The New Architecture does not mean that all React Native work happens synchronously. Rendering, scheduling, asynchronous APIs, and I/O still have their own concurrency and lifecycle constraints. The important change is that ordinary JavaScript-to-native calls no longer need to pass through the legacy serialized Bridge.

Platform Considerations

Both Android and iOS use separate JavaScript and native UI responsibilities, although their native module systems and platform APIs differ. The legacy Bridge served both platforms and did not introduce a feature-specific minimum OS version. Platform support is instead determined by the React Native version, its native build, and the APIs used by the application.

On Android and iOS, the current equivalents of Bridge-based communication are:

  • TurboModules for application-to-native module calls.

  • Fabric for rendering native views.

  • JSI as the low-level runtime interface that permits direct access to native objects from JavaScript.

Legacy native modules may still be supported through compatibility layers in some New Architecture configurations, but that does not make the old serialized Bridge the normal transport.

Interview Guidance

  • Describe the Bridge as a legacy, asynchronous, serialization-based boundary between JavaScript and native code.

  • Emphasize that it coordinated native-module calls, UI commands, callbacks, and events.

  • Do not present it as the primary mechanism in current React Native applications.

  • For new code, use standard React Native APIs, TurboModules, and Fabric rather than designing new communication directly around the Bridge.

19

In a React Native application, what is the safest way to protect API keys and other sensitive credentials from unauthorized access?

The safest approach is to avoid placing long-lived API keys in the React Native bundle at all and to perform privileged API calls from a backend, using secure storage only for short-lived, user-scoped credentials such as access tokens. Because a mobile app can be inspected, decompiled, or instrumented, no client-side secret should be treated as confidential.

Read full answer

Applies toReact Native 0.82 with the New Architecture and Hermes; Expo SDK 60 with `expo-secure-store`; bare React Native with `react-native-keychain`

Security Model

React Native JavaScript bundles are not confidential. Anyone who obtains the bundle can extract embedded strings, reverse-engineer logic, or instrument the running app.

Consequently, these approaches do not keep a secret confidential:

  • Hard-coded keys in JavaScript/TypeScript: strings survive bundling and can be read from the bundle.

  • @react-native-async-storage/async-storage: it is unencrypted persistent storage, not a secret manager.

  • EXPO_PUBLIC_* environment variables: values are embedded into the bundle at build time.

  • Bundle obfuscation: it slows inspection but does not prevent extraction.

  • Native configuration files readable by the app process: they are still present on the device.

Secure storage protects credentials at rest by using the platform’s protected storage facilities. It does not make a permanent global API key safe to distribute inside a client app.

Recommended Architecture

For new projects, use Expo and keep the following pattern:

  • Backend proxy: keep provider API keys on your server. The app sends authenticated requests to your backend, which calls the provider and applies authorization, rate limits, validation, and auditing.

  • Short-lived user credentials: store access tokens and, when necessary, refresh tokens in secure storage rather than in memory or plain storage.

  • Revocable credentials: access tokens should expire quickly and be renewed through the authentication flow.

  • Restricted provider keys: if a provider offers mobile-specific restricted keys, use them only as an additional defense, never as a replacement for a backend proxy.

  • HTTPS-only transport: communicate with your backend exclusively over HTTPS. Certificate pinning may be used as a supplementary control, but incorrect pinning can break releases.

Platform-Specific Storage

expo-secure-store uses iOS Keychain and Android Keystore-backed encryption. Secure storage is not provided by React Native core.

  • iOS 13+: Keychain items can be protected with device authentication. The requireAuthentication option can require Face ID, Touch ID, or the device passcode; it is not supported on Android.

  • Android 6.0+ / API 23+: credentials are protected using Android’s Keystore-backed encryption and the application sandbox. Hardware-backed Keystore availability varies by device.

  • Bare React Native: use a maintained native package such as react-native-keychain and configure it separately for iOS and Android.

Best Practices and Pitfalls

  • Never log tokens, authorization headers, decrypted credentials, or sensitive error payloads.

  • Remove credentials on logout or server-side session revocation.

  • Do not treat device authentication as proof that the current user is the original owner; combine it with server-side session validation.

  • Assume rooted, jailbroken, or otherwise compromised devices may expose runtime data.

  • Rotate and revoke credentials server-side. Secure storage cannot compensate for a credential that has already been extracted.

  • Avoid long-lived client secrets entirely. If a credential must live on the device, make it short-lived, scoped, and revocable.

20

How can you define reusable styles with a `StyleSheet` object and apply them to components in React Native?

Create reusable styles with StyleSheet.create() and apply its named entries through a component’s style prop. StyleSheet centralizes style definitions, validates them in development, and supports composing multiple styles through arrays.

Read full answer

Applies toReact Native 0.81 with New Architecture and Hermes

How It Works

  • Call StyleSheet.create() with an object whose keys are style names.

  • Each value contains related React Native style properties.

  • Apply a named style with an expression such as style={styles.container}.

  • Define static styles at module scope so they are created once rather than during every render.

  • Use an array when combining styles, such as style={[styles.card, highlighted && styles.highlightedCard]}.

  • Styles later in the array take precedence when they assign the same property.

Common Style Categories

Category Common properties
Layout flex, flexDirection, justifyContent, alignItems
Sizing width, height, minWidth, maxHeight, aspectRatio
Spacing margin, padding, marginTop, paddingHorizontal
Typography fontSize, fontWeight, lineHeight, color, textAlign
Borders borderWidth, borderRadius, borderColor
Effects opacity, transform, boxShadow, elevation, shadowColor, shadowOffset, shadowOpacity, shadowRadius

React Native’s layout model is based on Flexbox. Browser CSS also supports Flexbox, but React Native implements a CSS-like subset with platform-specific behavior. It has no CSS selector or pseudo-class system, and properties such as fontSize do not generally cascade from a View into its descendants as inherited CSS properties do.

Static, Dynamic, and Platform-Specific Styles

  • Static styles: Define these with StyleSheet.create() at module scope. This is the common case.

  • Dynamic styles: When values change at runtime, combine a named style with an inline style or create dynamic styles with useMemo().

  • Conditional styles: Use a boolean expression in a style array. React Native ignores false, null, and undefined entries.

  • Cross-platform shadows: With the New Architecture, boxShadow is available on Android and iOS.

  • Android elevation: elevation produces Android’s native elevation effect.

  • iOS shadows: shadowColor, shadowOffset, shadowOpacity, and shadowRadius provide the traditional iOS shadow appearance.

  • Platform selection: Platform.select() can choose platform-specific style objects or values.

Code Example

import { StyleSheet, Text, View } from 'react-native';

type ProfileCardProps = {
  name: string;
  highlighted?: boolean;
};

export function ProfileCard({
  name,
  highlighted = false,
}: ProfileCardProps) {
  return (
    <View
      style={[
        styles.card,
        highlighted && styles.highlightedCard,
      ]}
    >
      <Text style={styles.title}>{name}</Text>
      <Text style={styles.body}>
        React Native component styled with StyleSheet.
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#ffffff',
    borderColor: '#d1d5db',
    borderRadius: 12,
    borderWidth: 1,
    padding: 16,
  },
  highlightedCard: {
    borderColor: '#2563eb',
    borderWidth: 2,
  },
  title: {
    color: '#111827',
    fontSize: 20,
    fontWeight: '600',
  },
  body: {
    color: '#374151',
    fontSize: 16,
    lineHeight: 24,
    marginTop: 8,
  },
});

Best Practices and Pitfalls

  • Keep StyleSheet.create() calls outside render functions for static styles.

  • Avoid creating large style objects on every render; memoize genuinely dynamic styles.

  • Prefer named styles over repeated inline objects when the same style is reused.

  • Use flex: 1 when a component should fill the space allocated by its parent; it does not inherently mean “fill the entire screen.”

  • Test layouts on both Android and iOS because shadows, elevation, text rendering, and safe-area behavior differ.

  • Use StyleSheet.flatten() when code needs a resolved single style object rather than an array.

  • For long or virtualized data, use FlatList or SectionList; StyleSheet does not replace the appropriate list component.

21

What strategies enable responsive layout design using Flexbox in React Native?

React Native’s Flexbox supports responsive layouts by combining flexible sizing, relative dimensions, wrapping, and breakpoint-aware container styles instead of depending on a single fixed screen size. The same layout model applies on Android and iOS, although fonts, text metrics, and safe-area geometry still require platform testing.

Read full answer

Applies toReact Native 0.76 with New Architecture and Hermes; Expo SDK 52 for new projects

Core React Native Flexbox Behavior

  • Default direction: React Native uses flexDirection: 'column'; CSS normally starts with row. Set flexDirection: 'row' explicitly for horizontal layouts.

  • Flexible sizing: flexGrow distributes unused main-axis space, while flexShrink controls whether an item can contract. React Native defaults flexShrink to 0, unlike CSS, so overflowing children may require flexShrink: 1.

  • Flex shorthand: flex: 1 is a convenient way to make an item grow and shrink within the available main-axis space.

  • Style inheritance: Most styles, including View padding and background properties, are not inherited. Nested Text components can inherit supported text styles from ancestor Text components, including properties such as color and fontSize.

Responsive Layout Strategies

  • Use useWindowDimensions: Its width and height values are density-independent pixels and update when the window changes, including after an orientation change. Use them to select layout variants at breakpoints rather than checking orientation alone.

  • Prefer relative and bounded dimensions: Percentages are relative to the containing layout. Combine them with minWidth, maxWidth, minHeight, and maxHeight to prevent content from becoming unusably small or large.

  • Maintain proportions with aspectRatio: This is useful for images, media placeholders, and cards whose shape should remain consistent across screen sizes.

  • Use flex growth and wrapping: flexGrow and flexShrink allocate space among siblings, while flexWrap moves items onto additional lines along the main axis when space runs out.

  • Use gap for spacing: React Native 0.76 supports gap, rowGap, and columnGap, avoiding margin-based spacing that can produce uneven outer gaps.

  • Change the layout variant at breakpoints: Adjust flexDirection, item widths, or wrapping behavior based on useWindowDimensions. This lets phones use stacked content while tablets or landscape layouts use side-by-side content.

  • Use lists correctly: Render long collections with FlatList or SectionList rather than placing every item inside a ScrollView.

Safe Areas and Platform Differences

  • Use the maintained safe-area API: React Native core SafeAreaView is deprecated. Use SafeAreaView or useSafeAreaInsets from react-native-safe-area-context to account for iPhone sensors and home indicators, Android display cutouts, and other non-content areas.

  • Account for text measurement: Flexbox behavior is consistent across Android and iOS, but default fonts, font rendering, and line wrapping can differ. Test layouts on both platforms rather than assuming identical text dimensions.

  • Keep content reachable: Apply safe-area insets to the outer layout rather than adding the full inset to every child, which can create excessive spacing.

Best Practices and Common Pitfalls

  • Avoid fixed pixel dimensions for primary widths and heights; retain fixed values only for genuinely fixed elements such as icons or minimum touch targets.

  • Prefer normal flex flow for adaptive layouts, but use absolute positioning when its relationship to a responsive parent or safe-area inset is deliberate.

  • Define static styles with StyleSheet.create to centralize and reuse them, and avoid recreating large style objects during renders. This should not be presented as a special guarantee of the New Architecture.

  • Ensure a flex container has an understandable size constraint; flex: 1 cannot distribute space reliably when its parent has no bounded dimension.

  • Test both portrait and landscape layouts, small phones, tablets, split-screen windows, increased font sizes, and localized text.

22

What key factors must be accounted for when designing styles in React Native to ensure visual consistency across iOS and Android platforms?

Consistent React Native styling requires balancing a shared design system with platform-specific rendering behavior. Fonts, shadows, touch feedback, safe areas, accessibility scaling, and screen density must be considered so the interface remains intentional on both iOS and Android.

Read full answer

Applies toReact Native 0.81 with the New Architecture and Hermes; `react-native-safe-area-context` 5.x for safe-area handling

Shared Design System

  • Design tokens: Define shared colors, spacing, typography, radii, and elevation values so components use consistent design decisions rather than arbitrary literals.

  • Reusable components: Encapsulate common controls, text treatments, and interaction patterns. Use StyleSheet.create for static styles and separate dynamic values from the style definitions.

  • Purposeful divergence: Keep the overall visual language identical, but use Platform.select only where native conventions or rendering limitations require different values.

Native Rendering Differences

Area iOS Android
Default font San Francisco Roboto
Traditional shadow shadowColor, shadowOffset, shadowOpacity, and shadowRadius elevation
Touch feedback Usually an opacity change Usually a ripple configured through Pressable and android_ripple
Native controls Distinct defaults for text inputs, switches, pickers, and dialogs Distinct defaults for text inputs, switches, pickers, and dialogs
  • Shadows: React Native 0.81’s New Architecture also supports the cross-platform boxShadow property, which can provide a more consistent shadow definition. elevation remains useful for Android-specific elevation and stacking behavior.

  • Touch feedback: Use Pressable with an Android ripple and an appropriate iOS opacity treatment. TouchableNativeFeedback is legacy-oriented; prefer Pressable for new code.

  • Native components: Buttons, text inputs, pickers, and switches can have different intrinsic metrics and states. Prefer custom shared components when exact cross-platform appearance is essential.

Typography and Accessibility

  • Shared fonts: Bundle the same font family and corresponding weight files for both platforms, then configure them through the native font configuration. Selecting the default system font differently on each platform does not produce consistent typography.

  • Metrics: Test font weight, line height, letter spacing, and text rendering with real content. The same nominal font size can occupy different space on iOS and Android.

  • Accessibility: Honor system font scaling and test larger text sizes, bold text, and increased contrast. Avoid fixed text heights that clip scaled content.

Layout, Density, and System UI

  • Density independence: React Native dimensions are density-independent, but physical sizes still vary by display density and viewport. Prefer flexible layouts, constraints, and percentage-based dimensions rather than pixel-identical assumptions.

  • Safe areas: Core SafeAreaView is deprecated and primarily serves iOS. Use react-native-safe-area-context and useSafeAreaInsets to obtain the actual top, bottom, and horizontal insets where supported.

  • System chrome: Account for the status bar, navigation area, keyboard, notches, and Android edge-to-edge behavior. Do not hard-code status-bar or device heights.

  • Device range: Validate compact phones, large phones, tablets, landscape orientation, and different font and display settings.

Best Practices and Pitfalls

  • Minimize platform checks and document each intentional difference.

  • Test on both simulators and physical devices because fonts, shadows, rendering, and performance can differ.

  • Use automated visual-regression tests where practical, while retaining manual device checks.

  • For new projects, use Expo to simplify compatible native dependency setup. Use expo-router when file-based routing is wanted; it does not itself guarantee consistent native-module availability.

24

How should network requests be made in React Native with either the built-in fetch API or Axios?

Use the built-in fetch API for straightforward HTTP requests, or choose Axios when features such as interceptors, centralized configuration, automatic JSON handling, or consistent HTTP error objects are valuable. Both approaches return Promises and support cancellation with AbortController.

Read full answer

Applies toReact Native current stable release with the New Architecture and Hermes; Axios 1.x

Choosing Between fetch and Axios

  • fetch: Built into React Native, so it requires no additional dependency. It is a good default for small applications and simple CRUD operations.

  • Axios: Install with npm install axios. It provides timeouts, interceptors, request/response transformations, and structured error information.

  • HTTP errors: fetch does not reject for 4xx or 5xx responses, so explicitly check response.ok. Axios rejects non-2xx responses by default.

  • Cancellation: Prefer AbortController and its AbortSignal with both APIs. Axios’s older CancelToken approach should not be used for new code.

  • Expo: For new applications, Expo is recommended. The request patterns shown here work in Expo projects and Expo Go because neither fetch nor Axios requires a custom native module.

Code Example

The following fetch example loads a list and cancels the request if the component unmounts:

import React, { useEffect, useState } from "react";
import { FlatList, Text, View } from "react-native";

type User = {
  id: number;
  name: string;
};

export function UserList() {
  const [users, setUsers] = useState<User[]>([]);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    async function loadUsers() {
      try {
        const response = await fetch("https://api.example.com/users", {
          signal: controller.signal,
          headers: {
            Accept: "application/json",
          },
        });

        // fetch resolves for HTTP errors, so check the status explicitly.
        if (!response.ok) {
          throw new Error(`Request failed with status ${response.status}`);
        }

        const data = (await response.json()) as User[];
        setUsers(data);
      } catch (requestError) {
        // Do not report cancellation as a user-facing failure.
        if (!controller.signal.aborted) {
          setError(
            requestError instanceof Error
              ? requestError.message
              : "Unable to load users",
          );
        }
      }
    }

    void loadUsers();

    return () => controller.abort();
  }, []);

  if (error) {
    return <Text>{error}</Text>;
  }

  return (
    <View style={{ flex: 1 }}>
      <FlatList
        data={users}
        keyExtractor={(item) => String(item.id)}
        renderItem={({ item }) => <Text>{item.name}</Text>}
      />
    </View>
  );
}

With Axios, a shared client centralizes the base URL, timeout, and default headers:

import axios from "axios";

type User = {
  id: number;
  name: string;
};

const api = axios.create({
  baseURL: "https://api.example.com",
  timeout: 10_000,
  headers: {
    Accept: "application/json",
  },
});

export async function getUsers(signal: AbortSignal): Promise<User[]> {
  try {
    // Axios automatically parses the JSON response.
    const response = await api.get<User[]>("/users", { signal });
    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      if (error.response) {
        throw new Error(`Request failed with status ${error.response.status}`);
      }

      throw new Error(error.message);
    }

    throw error;
  }
}

Platform and Security Considerations

  • HTTPS: Use HTTPS for production APIs. Do not disable Android cleartext protection or modify iOS App Transport Security merely to bypass an HTTP configuration problem.

  • Android emulator: localhost refers to the emulator itself. Use 10.0.2.2 to reach the development machine’s HTTP server.

  • iOS simulator: localhost generally reaches services running on the same development computer.

  • Physical devices: Use the computer’s LAN address and ensure both devices can reach the port. Production traffic should still use HTTPS.

  • CORS: Native applications are not governed by browser CORS rules in the same way, but the API server can still restrict clients, origins, headers, or tokens.

  • Secrets: Mobile application binaries are not secure secret stores. Never embed private API keys; use a backend proxy or short-lived user authentication tokens.

Best Practices and Common Pitfalls

  • Set an explicit timeout so a failed connection cannot leave the UI loading indefinitely.

  • Distinguish HTTP failures, network failures, validation failures, and cancellations.

  • Avoid updating component state after unmount; use AbortController cleanup where appropriate.

  • Validate and encode user-controlled values, and validate all input again on the server.

  • Retry only transient failures and prefer idempotent operations such as GET; use backoff rather than immediate repeated requests.

  • Use FlatList or SectionList for long server-backed lists rather than rendering every item directly or placing them in a ScrollView.

25

How does GraphQL work, and what role can it play in a React Native application?

GraphQL is an API query language that lets a client request exactly the data it needs from a schema-defined endpoint. In React Native, it can replace or complement REST by giving mobile apps typed operations, predictable responses, and client-side caching for data fetched from remote services.

Read full answer

Applies toReact Native 0.78 with Hermes and the New Architecture; Apollo Client 4.x; graphql-js 16.x

Core Concepts

  • Schema: Defines the available types, fields, operations, and relationships.

  • Operation: A client request classified as a query, mutation, or subscription.

  • Type system: Documents the shape of operation results, helping catch mismatches during development.

  • Endpoint: Usually one HTTPS URL, such as https://api.example.com/graphql, handles all operations.

  • Response: GraphQL normally returns JSON containing data, and optionally errors.

Aspect GraphQL REST
Data fetching Client selects exact fields per request Fixed server response shapes
Endpoints Single URL Multiple URLs
Over-fetching Avoided Common
Under-fetching Avoided; one query can join related data Common; often needs several requests
Caching Client-side normalized cache HTTP-level caching
Typing Strongly typed schema Often loosely typed, optional OpenAPI

Compared with REST, GraphQL avoids over-fetching when a screen needs only a few fields and avoids multiple endpoint calls when related data is needed. It does not automatically reduce every request count: a complex query can still require multiple backend resolvers, and the server remains responsible for efficient data access.

React Native Usage

Apollo Client provides a conventional integration with hooks, normalized caching, mutations, and error handling. For a new application, Expo is recommended; the same GraphQL patterns work in a bare React Native project. Install graphql and @apollo/client, then configure one client at the application root. Use hooks such as useQuery for reads and useMutation for writes. Render large collections with FlatList or SectionList rather than ScrollView. The same GraphQL semantics apply on iOS and Android; platform differences primarily concern network availability, background execution, secure credential storage, and whether a connection is interrupted when the app moves to the background.

Caching and Mutations

  • Normalized cache: Apollo’s InMemoryCache stores objects by identifier and updates matching query results after a mutation.

  • Mutation payload: A mutation should return the changed object or enough identifying information for the cache to update itself; otherwise explicitly update or invalidate the relevant cache entries.

  • Fetch policy: The cache-and-network policy lets cached content appear immediately while fresh data is retrieved.

  • Offline support: For offline-first behavior, add a persistence layer and design the server’s cache behavior carefully.

Security and Error Handling

  • HTTPS: Use HTTPS for GraphQL endpoints.

  • Secrets: Never embed access tokens, API keys, or passwords in React Native source code.

  • Token storage: Store refresh tokens in platform-protected storage, such as expo-secure-store for Expo applications.

  • Authentication header: Attach authentication through the client’s HTTP link rather than constructing authorization headers separately in every component.

  • Error categories: Distinguish transport failures, authentication failures, GraphQL validation errors, and partial responses containing both data and errors.

  • Server validation: Validate all values sent as GraphQL variables on the server; client-side validation is not a security boundary.

Best Practices

  • Generated types: Use generated GraphQL types and typed documents when the project’s tooling supports them.

  • Field selection: Request only the fields each screen needs.

  • Variables: Use variables instead of interpolating values into GraphQL strings.

  • Pagination: Apply pagination to large collections.

  • Query depth: Avoid deeply nested queries that produce unexpectedly expensive backend work.

  • Monitoring: Configure production monitoring for GraphQL errors, latency, and operation-level metrics.

  • Schema contract: Treat GraphQL as an API contract: coordinate schema changes with server and mobile releases.

26

What approaches can a React Native application use to organize and manage different kinds of state?

React Native applications usually combine several state-management approaches rather than adopting one solution for every type of data. Local UI state, shared client state, server state, form state, navigation state, and persisted state each have different responsibilities and should be managed with the most appropriate tool.

Read full answer

Applies toReact Native 0.76 with the New Architecture and Hermes; Redux Toolkit 2.x, Zustand 5.x, Jotai 2.x, MobX 6.x, TanStack Query 5.x, and React Hook Form 7.x

State Categories

State type Examples Recommended approach
Local UI state Toggle visibility, selected tab, input value useState or useReducer
Shared client state Authentication session, feature flags, shopping cart Context, Redux Toolkit, Zustand, Jotai, or MobX
Server state API results, pagination, cache TanStack Query or another server-state library
Form state Validation, field values, submission state React Hook Form
Navigation state Current route, route parameters React Navigation
Persisted state Preferences, offline drafts, cached authentication data Async storage or platform-secure storage

Built-in Local State

  • useState: Best for simple, independent values such as a modal’s visibility, a counter, or a selected option.

  • useReducer: Best when several updates depend on one another or when state transitions are easier to express as actions and a reducer.

  • Component-local state: Keep state in the component that owns it whenever possible. This reduces coupling and limits re-rendering.

  • Derived values: Calculate values during rendering instead of storing duplicate state. For example, derive isEmpty from a list rather than maintaining both separately.

Local state should be moved upward only when multiple components genuinely need to coordinate it.

Context and Global Stores

  • React Context: Appropriate for stable, shared values such as theme, locale, authentication status, or dependency injection. Split unrelated contexts so a change in one does not notify every consumer of another.

  • Redux Toolkit: Suited to large, event-driven applications with predictable workflows, extensive debugging needs, or many cross-feature actions. Prefer createSlice, createAsyncThunk, and selector-based subscriptions.

  • Zustand: A lightweight option built around hooks and selectors. It works well when a feature needs a small shared store without Redux’s action and reducer structure.

  • Jotai: Uses atoms and derived atoms, making it convenient for fine-grained dependency graphs and state composed from smaller units.

  • MobX: Uses observable values and reactions. It is effective for highly interconnected state but introduces a reactive programming model that differs from React’s reducer-oriented model.

A store should expose narrowly selected values. A component subscribed to a selected slice generally avoids re-rendering when unrelated state changes.

Server, Form, and Persisted State

  • TanStack Query manages remote data, including caching, background refresh, retries, pagination, and mutation synchronization. API responses should normally remain in this cache instead of being copied into a global client store.

  • React Hook Form handles controlled form state, validation, dirty-field tracking, and submission state. Keeping form fields in global state usually causes unnecessary updates.

  • @react-native-async-storage/async-storage is the community package for asynchronous key-value persistence on Android and iOS. It is appropriate for preferences and non-sensitive drafts, but it is not encrypted storage.

  • Expo SecureStore is recommended for secrets in Expo applications. For bare React Native projects, use platform keychain or keystore solutions supported by the chosen library.

  • React Navigation owns navigation and route state. Pass identifiers through route parameters instead of duplicating an entire business object in global state.

Choosing an Approach

  1. Start with useState or useReducer.

  2. Introduce Context for relatively stable cross-tree values.

  3. Use a dedicated global store when state is shared, frequently updated, or governed by complex business rules.

  4. Use TanStack Query for remote data rather than treating API responses as ordinary global state.

  5. Use React Hook Form for forms and navigation libraries for route state.

  6. Add persistence only for data that must survive an application restart.

Best Practices and Pitfalls

  • Avoid copying the same data into component state, Context, and a global store; establish one source of truth.

  • Keep frequently changing visual values out of Context. For high-frequency or gesture-driven updates, consider React Native Reanimated shared values.

  • Normalize large collections when updates require frequent insertion, deletion, or lookup.

  • Keep reducers, selectors, and store actions pure and test them independently.

  • Use native storage through the community or official packages rather than attempting direct filesystem access without platform-specific code.

  • Store passwords, tokens, and other secrets in Android Keystore-backed or iOS Keychain-backed storage, not AsyncStorage.

  • For new projects, prefer Expo unless native build customization is required; it simplifies React Native, Expo SecureStore, and other platform integrations.

27

What approach would you take to implement offline data synchronization in a React Native application, ensuring data consistency once connectivity is restored?

Handling offline synchronization requires an offline-first architecture where the local device state is treated as the source of truth, and changes are queued for server reconciliation when connectivity returns. This approach ensures the app remains usable during network outages while maintaining data integrity through structured conflict resolution strategies.

Read full answer

Applies toReact Native 0.74+ with New Architecture and Hermes, Expo SDK 51+, @react-native-async-storage/async-storage 1.2+, @react-native-community/netinfo 11+

Core Strategy

For new projects, Expo is recommended as it simplifies native dependencies and manages background tasks consistently. The core flow involves writing data to local storage immediately, marking it as pending, and attempting server transmission only when the network is available. This guarantees UI responsiveness regardless of connection status. Background sync should be used cautiously due to OS restrictions on background processes, which vary significantly between mobile platforms.

Local Persistence and Network Detection

AsyncStorage from @react-native-async-storage/async-storage suits simple key-value queues, while relational data requires SQLite via expo-sqlite or react-native-sqlite-storage. Network status is monitored using @react-native-community/netinfo. On Android, Doze mode may delay background sync, whereas iOS restricts background networking more aggressively. Always check state.isConnected and state.isInternetReachable for accuracy, as captive portals may report connection without internet access.

Sync Logic and Conflict Resolution

Maintain a FIFO mutation queue stored locally. When connectivity restores, dequeue items and send them sequentially to avoid race conditions. Implement exponential backoff for failed requests to prevent battery drain. Handle HTTP 409 conflicts by comparing server timestamps or version vectors. Simple last-write-wins logic risks data loss; operational transforms or merge strategies are safer for collaborative data. Ensure API endpoints are idempotent to prevent duplicate records during retries.

Best Practices

  • Encrypt sensitive local data using expo-secure-store instead of plain AsyncStorage.

  • Test offline scenarios using Chrome DevTools or physical airplane mode.

  • Avoid storing large binary blobs in AsyncStorage; use the filesystem instead.

  • Always clear successfully synced items from the queue to prevent infinite loops.

  • On Android, request ACCESS_NETWORK_STATE if needed, while iOS requires background modes entitlements for prolonged sync.

28

In what ways can developers implement operating system-specific logic and UI within a single React Native codebase?

React Native supports operating-system-specific behavior through runtime platform branching, Metro’s platform-specific module resolution, and native iOS and Android integrations, all from one repository. Shared logic should remain common while behavior and UI diverge only where platform conventions require it.

Read full answer

Applies toReact Native 0.79 with the New Architecture and Hermes; Expo SDK 53 for Expo-specific workflows

Runtime Platform Branching

The Platform API from react-native exposes the current operating system through Platform.OS, which returns values such as 'ios' and 'android'. Platform.select() accepts an object keyed by platform and returns the value matching the current OS, keeping conditional expressions out of component bodies.

Platform.Version provides a platform-dependent operating-system version when version-specific behavior is necessary. OS-specific system behavior should use the appropriate API rather than being inferred indirectly. For example, BackHandler handles Android’s hardware Back button; iOS has no corresponding operating-system Back event.

Platform-Specific File Resolution

Metro can resolve different implementations from the same import by examining file extensions. On iOS, an import of Button can resolve to Button.ios.tsx; on Android, it can resolve to Button.android.tsx. If no platform-specific file exists, Metro tries Button.native.tsx, which applies to both native platforms, before falling back to Button.tsx.

The selected implementation is included in the target platform’s bundle, while the implementation for the other platform is normally omitted. This mechanism is useful when an entire component requires a materially different layout, interaction, or native convention, but it should not replace sharing components that are substantially the same.

Native Platform Integration

A React Native repository can contain native ios/ and android/ projects alongside the JavaScript or TypeScript application. Developers can implement functionality directly in Swift or Objective-C for iOS and Kotlin or Java for Android when the required capability is not adequately available in JavaScript.

For reusable native functionality, the New Architecture provides Turbo Native Modules. TypeScript specification files are processed by React Native’s code generation tooling to produce strongly typed interfaces and native implementations. TurboModules use JSI-based access and can expose synchronous or asynchronous operations. Existing legacy Native Modules can continue to operate through the New Architecture interoperability layer, although new integrations should generally use TurboModules.

Expo Workflows

Expo is recommended for new projects. Platform branching and Metro file resolution work normally in Expo applications, while Expo Modules API and config plugins can expose or configure native capabilities without maintaining separate platform repositories.

For projects requiring custom native projects, Continuous Native Generation can generate the ios/ and android/ directories with npx expo prebuild. Native configuration should be represented through config plugins, or the generated native projects should be committed and maintained deliberately, so subsequent generation does not make changes difficult to reproduce.

Best Practices and Pitfalls

Keep shared state, networking, and business logic platform-neutral. Put platform decisions at component or module boundaries, and use separate files when an entire implementation differs rather than scattering checks throughout shared components.

Follow each platform’s navigation, gesture, accessibility, and back-button conventions instead of attempting to make their behavior identical. On React Native 0.79 with the New Architecture, the boxShadow style can provide consistent shadows across platforms, although platform-specific styles remain appropriate where designs intentionally differ.

Test on both Android and iOS with real-device testing in addition to simulators, because fonts, safe areas, navigation behavior, and system interactions can differ. Avoid relying on user-agent strings or other indirect signals to identify the operating system.

29

Which React Native user interface elements and system modules typically require separate handling or distinct implementations depending on whether the application targets iOS or Android?

React Native manages operating system divergence primarily through the Platform module, allowing developers to conditionally render UI or apply styles, while many formerly platform-specific core components have been deprecated in favor of unified community libraries.

Read full answer

Applies toReact Native 0.76+ with New Architecture, Expo SDK 52+

Detecting the Operating System

The Platform module from react-native provides the primary API for identifying the host environment. Platform.OS returns a string, typically 'ios' or 'android', while Platform.Version provides the operating system version number, which is useful for checking minimum API levels or iOS versions. For selecting values without conditional logic, Platform.select({ ios: ..., android: ..., default: ... }) returns the value matching the current platform, falling back to default if no match is found. This approach is preferred over manual branching when configuring styles or configuration objects.

Legacy Components and Modern Replacements

Several components were originally split by platform but have since been removed from the core library to reduce maintenance burden. Developers must now install community packages for these features. The table below outlines common transitions:

Legacy Component Status Recommended Replacement
DatePickerIOS / DatePickerAndroid Removed from core @react-native-community/datetimepicker
Picker (core) Removed from core @react-native-picker/picker
NavigatorIOS Deprecated @react-navigation/native-stack
StatusBar (props differ) Core Use StatusBar with platform-specific props

For example, date selection now uses a single component from @react-native-community/datetimepicker that adapts its UI natively on each platform, rather than importing different components based on the OS.

Conditional Rendering Strategies

In function components, platform checks should be performed during render to decide which component tree to mount. While ternary operators work well for simple cases, extracting platform-specific logic into separate components improves readability. When styling, StyleSheet.create can accept Platform.select values directly, which is more performant than inline style objects because the styles are resolved once during initialization rather than on every render. Avoid placing heavy logic inside the render path; instead, compute platform-dependent values outside the JSX return statement.

Best Practices and Pitfalls

Minimize platform branching to keep the codebase maintainable; rely on native adaptations within community libraries whenever possible. Always test on both physical devices and simulators, as visual discrepancies often appear only on specific OS versions. When checking Platform.Version, remember that Android version numbers do not always map linearly to API levels, so prefer feature detection where available. Finally, ensure all community packages are linked correctly; with the New Architecture and Expo, most packages support auto-linking, but native configuration may still be required for deep platform integrations.

30

How do you handle operating system-specific logic and styling using the Platform API in React Native?

The Platform module in React Native provides a unified interface to detect the operating system and adapt code behavior or styling accordingly. By importing it from the core package, you can access properties like OS and Version or use select to choose values based on the runtime environment without external dependencies, ensuring compatibility across iOS and Android.

Read full answer

Applies toReact Native 0.74+

How Platform Detection Works

React Native bridges native code to JavaScript, allowing the Platform module to query the underlying OS at runtime. This detection happens during render or logic execution, enabling conditional rendering of components or application of specific styles. Under the New Architecture, this behavior remains consistent, relying on the JSI bridge for efficient property access. It is essential for handling design differences, such as navigation bars or font rendering, between iOS and Android.

Key Properties and Methods

  • Platform.OS: Returns a string, usually 'ios' or 'android', identifying the current operating system.

  • Platform.Version: Returns the OS version as a string (e.g., iOS version number or Android API level).

  • Platform.select: Takes an object with platform keys and returns the value matching the current OS, falling back to default.

Code Example

import { Platform, StyleSheet, View, Text } from 'react-native';

const App = () => {
  // Select font size based on platform, defaulting to 16
  const fontSize = Platform.select({
    ios: 18,
    android: 16,
    default: 16,
  });

  return (
    <View style={styles.container}>
      <Text style={[styles.text, { fontSize }]}>
        Running on {Platform.OS}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    // Adjust padding for iOS status bar height differences
    paddingTop: Platform.OS === 'ios' ? 50 : 20,
  },
  text: {
    color: 'black',
  },
});

export default App;

Best Practices

  • Use Platform.select for style values to keep StyleSheet definitions declarative.

  • Recommend Expo for new projects, as it abstracts many native configuration issues while still exposing Platform.

  • Group platform-specific logic into separate components to keep the main tree readable.

  • Test on both simulators and physical devices, as version reporting may differ.

  • Utilize FlatList for long lists regardless of platform, avoiding ScrollView for performance.

Common Pitfalls

  • Do not treat Platform.Version as a number; parse it with parseInt if numeric comparison is needed.

  • Avoid overusing platform checks; shared code reduces maintenance costs significantly.

  • When using community modules (e.g., @react-native-async-storage/async-storage), verify their platform support separately since Platform only reflects the core environment.

31

What techniques enable a React Native component to display distinct user interface elements based on whether the application is running on Apple or Google mobile operating systems?

To render distinct UI elements based on the operating system, you primarily rely on the Platform API provided by React Native core. This allows you to conditionally render components or apply specific styles depending on whether the app runs on iOS or Android within a single shared codebase.

Read full answer

Applies toReact Native 0.76+ (New Architecture enabled by default) and Expo SDK 52+

Runtime Detection with Platform API

The most common approach is inspecting Platform.OS at runtime, which returns the string 'ios' or 'android'. You can use this value inside a function component to conditionally return JSX or calculate styles. Additionally, Platform.select() accepts an object with ios and android keys and returns the value matching the current platform, which is cleaner for style objects or configuration values rather than entire component trees. This method keeps all logic in one file, making it easier to maintain when differences are minor.

Platform-Specific File Extensions

Metro bundler supports resolving platform-specific files automatically. If you create Header.ios.tsx and Header.android.tsx, importing ./Header will load the correct file based on the target platform without any conditional logic inside the component. This is ideal when the implementation differs significantly, such as navigation headers or date pickers, keeping the shared code free of platform checks. However, this approach can lead to code duplication if overused, so reserve it for substantial UI divergences.

Styling and Layout Nuances

Visual differences often stem from default styling behaviors rather than component logic. For example, iOS uses shadow props while Android relies on elevation. When using Platform.select for styles, ensure you define a fallback key like default to handle unexpected platforms or web targets if using React Native Web. Always test layout changes on both simulators and physical devices because font rendering and touch targets may vary between operating system versions.

Best Practices and Pitfalls

  • Avoid fragmenting your codebase unnecessarily; prefer shared components with minor platform adjustments over entirely separate files unless the UX patterns diverge fundamentally.

  • When using Expo, the expo-device package can provide more granular device information, but Platform.OS remains sufficient for OS-level distinctions.

  • Conditional rendering based on Platform.OS happens during render, so heavy logic should be memoized with useMemo to prevent performance drops on re-renders.

  • Ensure accessibility labels remain consistent across platforms regardless of visual differences.

33

Which techniques can be used to improve the performance of a React Native application?

Effective React Native optimization begins with profiling, followed by targeted improvements to rendering, lists, animations, networking, startup time, and native integration. The New Architecture and Hermes provide a modern foundation, but application-level bottlenecks still require measurement and deliberate design.

Read full answer

Applies toReact Native 0.81 with the New Architecture and Hermes; Expo SDK 54 for new projects (Android 7.0/API 24 or later, iOS 15.1 or later)

Measure Before Optimizing

  • Profile a release or production-like build; development mode adds overhead that can hide real performance problems.

  • Use the React DevTools Profiler to inspect component render and commit behavior, and the Hermes sampling profiler in React Native DevTools to inspect JavaScript CPU call stacks, expensive functions, and long-running tasks.

  • Use Android Studio Profiler on Android and Xcode Instruments on iOS for CPU, memory, frame timing, and native-code analysis.

  • Test on a representative low-end device as well as a modern device. Averages can conceal severe performance on weaker hardware.

  • Establish metrics such as startup duration, frame rate, memory consumption, network latency, and interaction responsiveness before making changes.

Reduce Rendering and State Updates

  • Keep state as local as possible. A state change in a parent component often causes all of its children to render again.

  • Split large contexts so that consumers subscribe only to the state they need. A single application-wide context containing frequently changing values can cause broad rerenders.

  • Use React.memo for components that are expensive to render and receive stable props. Do not memoize every component indiscriminately, because comparison overhead may exceed the benefit.

  • Use useMemo and useCallback selectively for genuinely expensive calculations or stable object and function references.

  • Avoid creating large collections, deep transformations, or expensive formatting during every render. Move such work to a memoized value, a worker-like background task, or a native implementation.

  • Avoid unnecessary state updates and derive values during render when no synchronization problem requires storing them.

Choose the Right List and UI Strategy

Requirement Recommended approach
Long or unbounded data set FlatList
Grouped or sectioned data SectionList
Small, fixed number of items Regular component mapping is sufficient
  • Do not place long lists inside a ScrollView; this defeats list virtualization and can cause high memory usage and poor scrolling.

  • Provide stable keys and avoid recreating row components unnecessarily.

  • Use getItemLayout only when item height is fixed or can be calculated reliably.

  • Tune initialNumToRender, maxToRenderPerBatch, windowSize, and updateCellsBatchingPeriod based on measurements rather than applying arbitrary values.

  • On Android, removeClippedSubviews may reduce memory consumption for large lists, but it can cause clipping or disappearing-content issues, so test it carefully.

Optimize Animations and Images

  • Use React Native’s Animated API with useNativeDriver: true when animating supported properties such as transform and opacity. Moving animation work off the JavaScript thread produces smoother interactions on both Android and iOS.

  • Keep animation state outside the normal React render path when possible, and avoid triggering unrelated state changes on every animation frame.

  • Do not animate unsupported layout properties with the native driver. They may still be animated through JavaScript, but at a higher runtime cost.

  • Resize images to their displayed dimensions, compress assets appropriately, and avoid loading full-resolution files into memory.

  • Use consistent image placeholders to prevent layout shifts, and cache remote images when the application repeatedly displays the same content.

  • Comprehensively test shadows, transparency, blur effects, and large images on both platforms; these can be particularly expensive to composite.

Improve Startup, Networking, and Native Work

  • Keep startup work small: defer optional features, avoid importing heavy modules immediately, and lazy-load screens that are not required on the first screen.

  • Use release bundles and Hermes rather than evaluating development-oriented code paths.

  • Paginate large API responses, cache stable data, coalesce duplicate requests, and avoid request waterfalls.

  • Perform JSON parsing and other CPU-intensive transformations away from the main and JavaScript threads when possible.

  • With the New Architecture, use Fabric for native UI components and TurboModules for new native integrations where appropriate. Legacy NativeModules remain available through the New Architecture interoperability layer for compatibility in React Native 0.81, but new integrations should target TurboModules rather than rely on legacy-architecture behavior.

  • Keep native module calls asynchronous unless a synchronous API is essential; synchronous work can block UI responsiveness.

Practical Priorities

  1. Find the largest bottleneck using profiling.

  2. Fix unnecessary rerenders and unbounded list growth first.

  3. Move animation and compute-heavy work off the JavaScript thread.

  4. Reduce startup and network work.

  5. Re-measure after each change and verify both Android and iOS behavior on physical devices.

34

What strategies can I use to minimize the size of a React Native application bundle?

Reducing bundle size starts with measuring the production artifact, removing unused JavaScript and native dependencies, optimizing media assets, and using Android and iOS delivery features that ship only the resources a device needs. The largest gains usually come from dependency and asset audits rather than from rewriting application code.

Read full answer

Applies toReact Native 0.76+ current stable release with New Architecture and Hermes

Measure the Production Artifact

  • Analyze a release build, not the development bundle, because development logging, source maps, and unoptimized assets can substantially increase size.

  • For Android, distinguish between:

  • AAB: the upload artifact; Google Play uses it to generate device-specific downloads, so it is not the installed size.

  • Device-specific APK: generated per device configuration such as ABI, screen density, and language.

  • For iOS, measure the App Store-thinned .ipa installed on a representative device, not the archive size.

  • Break down the total into JavaScript bundle, assets, native binaries, and platform resources before choosing optimizations.

Artifact What it represents Use for size decisions
Android AAB Upload package Not user download size
Android device APK Generated per device Actual download for that device
iOS archive Full build Not thinned download
iOS device .ipa App Store delivery Actual download for that device
Metro JS bundle JavaScript only Isolates JS cost
  • You can generate a standalone production JavaScript bundle with react-native bundle --platform android --dev false --minify true --entry-file index.js --bundle-output build/index.android.bundle --assets-dest build/android-assets, plus the iOS equivalent, to isolate JS and asset costs, but final native artifacts remain authoritative.

Reduce JavaScript and Dependencies

  • Remove unused packages: Every dependency can add JavaScript, assets, and native binaries. Audit with npm ls and visualizers such as source-map-explorer.

  • Avoid duplicative libraries: Choose one package per responsibility and prefer pure-JavaScript or platform-neutral packages when they satisfy the requirement.

  • Use production minification: Release builds minify code; also remove console statements and development-only logging from release paths.

  • Keep Hermes enabled: Hermes compiles JavaScript to bytecode and improves startup; do not disable it purely to chase size numbers.

  • Keep New Architecture enabled: It is the default in current releases, and removing it is not a recommended size strategy.

  • Treat dynamic import code splitting cautiously: Metro and React Native do not treat browser-style code splitting as a reliable initial-bundle lever; verify behavior in your release before designing around it.

Optimize Assets

  • Resize images: Store images near their displayed dimensions instead of shipping camera-resolution originals.

  • Use appropriate formats: Compress PNGs, use JPEG for opaque photographs, and avoid unnecessary transparency.

  • Avoid base64-encoded images: Base64 adds roughly one-third overhead and prevents efficient caching and reuse.

  • Remove unused assets: Audit image catalogs, animations, sounds, videos, documents, and build-copied assets.

  • Subset fonts: Include only required glyphs, weights, and font files.

  • Compress audio and video: Use platform-supported codecs and remove silence or unused tracks.

  • Use SVG selectively: react-native-svg is good for icons, but many large vector illustrations can increase JS bundle and rendering cost; pre-render simple static artwork as raster images when practical.

Account for Native Dependencies

Native libraries often contribute more size than application JavaScript because they ship Android .so files and AARs, plus iOS frameworks.

  • Audit native dependencies and remove packages whose functionality is unused.

  • Prefer libraries with maintained New Architecture support when native code is required.

  • On Android, enable R8 minification for unused Java/Kotlin code and resources, and confirm React Native keep rules are present.

  • On iOS, confirm the Release configuration enables dead-code and symbol stripping.

  • Do not manually remove architectures or symbols unless you target a fixed device environment and accept the compatibility risk.

  • Rebuild after upgrading or replacing a native dependency; Metro JS analysis will not show its .so or framework contribution.

Use Platform Delivery Features

  • Android: Upload an AAB so Google Play generates device-specific APKs and splits by language, density, and ABI. The set of supported ABIs and density buckets directly affects download size.

  • iOS: Rely on App Store Connect thinning for device-specific .ipa delivery and test on representative devices.

  • Expo: For new projects, start with npx create-expo-app@latest, add dependencies with npx expo install, and evaluate size from an EAS production build.

Best Practices and Pitfalls

  • Measure after every meaningful change and keep before-and-after figures for JS bundle, assets, native libraries, and final download.

  • Test installation and startup on representative devices; archive or AAB size does not predict actual user download size.

  • Keep Android mapping files for Play Console symbolication, but do not ship them inside the app.

  • Remove unused resources from both the repository and platform asset catalogs.

  • Do not disable Hermes, drop supported ABIs, or strip native code merely to obtain a smaller reported number.

  • These techniques do not require raising the declared minimum iOS version or Android API level.

35

How does the PureComponent class optimize rendering performance in React Native, and what is the recommended modern approach for function components?

PureComponent avoids unnecessary rendering by shallowly comparing the component’s next and previous props and state; when they are equivalent, React skips the render and reuses the previous result. For function components, the modern equivalent is React.memo, which shallowly compares props by default and should be used selectively after profiling.

Read full answer

Applies toReact Native 0.82 with React 19.1, New Architecture and Hermes

How PureComponent Optimizes Rendering

  • Shallow comparison: PureComponent extends React.Component and implements shouldComponentUpdate using a shallow comparison of props and state.

  • Equality rules: Primitive values are compared with Object.is. Objects, arrays, and functions are compared by identity, so a new reference is normally treated as a change even if its contents are identical.

  • Render bailout: If the shallow comparison finds no differences, React skips rendering the component and reuses its previous rendered result.

  • No deep comparison: Mutating an existing object or array in place does not create a new reference, so the optimization may not detect that change.

  • Status: PureComponent is not removed or formally deprecated, and class components remain supported. Function components are nevertheless the recommended model for new React Native code.

Modern Function-Component Approach

React.memo is a higher-order component that caches a function component’s rendered result. With its default comparison, it checks props with the same shallow-equality rules but does not compare the component’s local state.

Behavior PureComponent React.memo
Component style Class Function
Default comparison Props and state Props only
Own state update Compared as part of the class state Causes the memoized component to render
Custom comparison Override shouldComponentUpdate Supply a custom comparison function

Hooks such as useCallback and useMemo can preserve prop identities, but they do not replace React.memo and should not be added indiscriminately.

Code Example

import React, { memo, useCallback, useState } from 'react';
import { Pressable, Text, View } from 'react-native';

type ListItemProps = {
  title: string;
  onPress: () => void;
};

const ListItem = memo(function ListItem({ title, onPress }: ListItemProps) {
  return (
    <Pressable onPress={onPress}>
      <Text>{title}</Text>
    </Pressable>
  );
});

export default function ParentComponent() {
  const [count, setCount] = useState(0);

  // Keep the callback identity stable across parent renders.
  const handlePress = useCallback(() => {
    setCount(current => current + 1);
  }, []);

  return (
    <View>
      <Text>Count: {count}</Text>
      <ListItem title="Increase count" onPress={handlePress} />
    </View>
  );
}

When count changes, ParentComponent renders again, but ListItem‘s title and onPress props retain the same values and identity. The memoized child can therefore skip rendering.

Best Practices and Pitfalls

  • Profile first: Memoization adds comparison and retained-result overhead. Optimize components that render frequently or have expensive output.

  • Preserve identity intentionally: Use useCallback for callbacks passed to memoized children and useMemo for objects whose identity affects comparison or effects.

  • Do not mutate shared data: Shallow comparison detects reference changes, not in-place mutations. Create new objects and arrays when their values change.

  • Remember state behavior: A React.memo component still renders when its own state changes because state is not part of the default props comparison.

  • Use React Native’s list APIs: For long lists, prefer FlatList or SectionList; memoized row components can complement, but not replace, virtualization.

  • Prefer Expo for new projects: Use the current Expo SDK and its supported React Native version unless the project specifically requires a bare React Native setup.

36

How does the Hermes JavaScript engine improve the runtime performance of React Native applications?

Hermes is React Native’s default JavaScript engine and improves runtime performance mainly by compiling JavaScript to compact bytecode ahead of time, compiling functions lazily, and providing a JSI runtime that lets the New Architecture call native code synchronously without the legacy bridge.

Read full answer

Applies toReact Native 0.78 and later (current stable release series) with Hermes and the New Architecture enabled by default; Expo's current SDK for new projects.

How Hermes Works

Hermes replaces the legacy JavaScriptCore-based execution path on both Android and iOS. Its execution model includes:

  • Ahead-of-time bytecode: Release builds compile JavaScript to Hermes bytecode with the hermes compiler, so the runtime can skip parsing and source compilation.

  • Lazy compilation: Functions are compiled only when first called, limiting startup work to code that actually executes.

  • JSI runtime: Hermes implements jsi::Runtime, allowing native C++ modules and JavaScript to exchange values directly instead of serializing messages across an asynchronous bridge.

  • Mobile-focused runtime: A smaller heap, optimized garbage collector, and reduced metadata lower memory pressure compared with general-purpose engines.

Hermes bytecode is tied to the Hermes version, so precompiled artifacts must be regenerated after upgrading React Native or Hermes.

Performance Benefits

  • Faster cold startup: Bytecode loading and lazy compilation reduce main-thread work before the first render; gains grow with bundle size.

  • Lower memory usage: A smaller runtime footprint helps low-memory Android devices and improves background app preservation.

  • Reduced serialization overhead: With the New Architecture, JSI avoids repeated JSON-like serialization for frequent native calls.

  • More predictable execution: Ahead-of-time bytecode and mobile-oriented compilation reduce JIT warm-up variability across devices.

  • Smoother interactions: Lower JavaScript overhead benefits animations, gesture handling, and large-list updates.

Hermes alone does not guarantee faster operations. Component design, state updates, native-module architecture, and device capability still dominate perceived performance.

Hermes Compared with JavaScriptCore

Area Hermes JavaScriptCore
React Native default Yes No, legacy alternative
Compilation Ahead-of-time bytecode plus lazy compilation JIT-based execution at runtime
Memory footprint Generally lower Generally higher
Cold startup Usually faster with bytecode Often slower because of runtime compilation
JSI runtime Built-in default Available only through non-default JSCRuntime builds
Recommendation Preferred for new and existing apps Only for special compatibility cases

The New Architecture and Hermes are separate improvements: Hermes improves JavaScript execution, while the New Architecture improves native integration. They complement each other but should not be treated as the same feature.

Android and iOS Behavior

Hermes runs on both platforms, so JavaScript execution characteristics are broadly consistent. Platform differences remain in native rendering, networking, permissions, storage, and operating-system APIs.

For React Native 0.78 and later:

  • Android requires API level 24 or later.

  • iOS requires iOS 15.1 or later.

These are React Native platform requirements rather than restrictions introduced by Hermes.

Best Practices and Common Pitfalls

  • Use Expo for new projects: Expo’s current SDK enables Hermes and the New Architecture with a compatible configuration.

  • Measure on real devices: Use React Native DevTools and platform profilers; emulators can hide memory and startup differences.

  • Keep release bundles optimized: Remove unused dependencies, avoid unnecessary synchronous initialization, and use FlatList or SectionList for large collections.

  • Do not credit Hermes for bridge fixes: JSI and the New Architecture reduce bridge overhead; Hermes supplies the JSI runtime, but poor module design still causes bottlenecks.

  • Regenerate bytecode artifacts on upgrades: Hermes bytecode should not be reused across incompatible React Native or Hermes versions.

  • Avoid premature micro-optimization: Stable state updates, memoization, and efficient data structures usually affect perceived performance more than small JavaScript-level changes.

37

Which testing tools are most commonly used to test React Native applications?

The main testing stack for React Native combines Jest, React Native Testing Library, and an end-to-end framework such as Detox, Maestro, or Appium. React Native Testing Library is generally preferred for component and integration tests, while Detox, Maestro, and Appium cover workflows that interact with the native application.

Read full answer

Applies toReact Native 0.82 with the New Architecture and Hermes; Jest 30.x and React Native Testing Library 13.x; Detox 20.x, Maestro 2.x, and Appium 3.x; Expo SDK 54 with `jest-expo` 54.x

Common Testing Tools

Tool Primary use Why teams choose it
React Native Testing Library Component and integration testing Tests components through rendered output and user interactions rather than implementation details
Jest Unit testing and test runner Provides the test environment, assertions, mocking, coverage, and the react-native preset
Detox Native end-to-end testing Synchronizes test actions with the React Native application and supports gray-box testing
Maestro Cross-platform end-to-end testing Uses readable YAML flows and is especially convenient for black-box mobile workflows
Appium Cross-platform end-to-end testing Uses WebDriver-based automation and supports a broad range of devices and platforms
jest-expo Testing Expo projects Provides a Jest preset configured for Expo dependencies and runtime

React Native Testing Library and Jest are usually used together: React Native Testing Library queries and exercises components, while Jest executes the tests and supplies the test runtime.

Choosing the Right Level of Testing

  • Unit tests: Use Jest for isolated functions, utilities, reducers, and business rules that do not require rendering a component. Test hooks with a hook-testing utility such as React Native Testing Library’s renderHook.

  • Component and integration tests: Use React Native Testing Library to test React Native components, navigation interactions, state changes, accessibility behavior, and mocked native-module boundaries.

  • End-to-end tests: Use Detox when tests need synchronization with React Native internals, or Maestro or Appium when black-box user journeys are more important.

For example, a component test might verify that pressing a button updates visible text. A Detox, Maestro, or Appium test might sign in through the native UI, open a screen, submit a form, and confirm that data appears after an API request.

React Native and Platform Considerations

  • The New Architecture does not replace JavaScript-level testing. Native module boundaries should be mocked at focused test seams, while behavior that depends on real native integration needs device or emulator coverage.

  • Hermes versus Jest: Hermes executes the built application’s JavaScript, including in end-to-end tests. Jest unit and component tests normally run in a Node-based JavaScript environment rather than inside the packaged Hermes runtime.

  • Detox is a strong choice for teams that want deterministic synchronization and direct knowledge of React Native elements on iOS and Android. It requires suitable simulator or emulator environments and may have stricter device and operating-system requirements than Maestro.

  • Maestro is well suited to concise black-box smoke tests on iOS and Android. Its flows should use stable visible text, test IDs, or accessibility identifiers.

  • Appium is appropriate when a shared WebDriver-based test suite must cover iOS and Android applications across vendors, although its setup and device-driver maintenance are comparatively involved.

React Native 0.82 requires Android 7.0 (API 24) or later and iOS 15.1 or later. The chosen end-to-end tool may impose stricter minimum versions, so its current support matrix should be checked against the application’s CI devices.

Best Practices and Common Pitfalls

  • Prefer user-visible behavior: Query by accessible role, label, text, or test ID instead of component state or private implementation details.

  • Keep tests isolated: Reset mocks, timers, and persisted state between tests so that execution order does not affect results.

  • Mock selectively: Mock native modules and external services at their boundaries. Excessive mocking can allow an integration test to pass while the packaged application fails.

  • Use the project’s preset: Standard React Native projects use Jest’s react-native preset, while Expo projects use jest-expo.

  • Start new projects with Expo: Expo is the recommended starting point for new React Native projects; use its jest-expo preset when configuring tests.

  • Do not rely only on end-to-end tests: They are slower and harder to diagnose. Combine them with fast unit tests and React Native Testing Library integration tests.

  • Avoid deprecated matcher packages: React Native Testing Library 13 includes the commonly used Jest matchers, so the deprecated @testing-library/jest-native package is unnecessary and should not be newly introduced.

38

What are the commonly used approaches to debug a React Native application during development?

Common React Native debugging approaches combine React Native DevTools, Metro logging, in-app error feedback, and native debuggers. The most effective workflow separates JavaScript and React issues from native Android or iOS issues.

Read full answer

Applies toReact Native 0.81 with the New Architecture and Hermes; Expo SDK 54

Metro and React Native DevTools

Metro is the development bundler and displays bundle activity, JavaScript logs, warnings, and errors in its terminal. Press j in the Metro terminal to open React Native DevTools, which provides:

  • A component tree with props, state, hooks, and context inspection.

  • A console for reviewing JavaScript output.

  • Performance and re-render analysis.

  • Breakpoints and source-mapped debugging for Hermes.

  • A Network panel for requests made through supported JavaScript networking APIs.

The developer menu provides reload, inspection, and performance-related commands. On simulators, use Cmd+D on iOS or Cmd+M on Android; on physical devices, shake the device in a development build.

Logging and Error Feedback

Use console.log, console.warn, and console.error while diagnosing JavaScript behavior. Warnings and errors are surfaced through Metro and React Native DevTools, while development builds also provide on-screen error feedback for some failures.

Keep logging focused and remove unnecessary logging from production builds. The global __DEV__ flag can guard development-only logging without introducing another logging dependency. Avoid logging credentials, tokens, personal information, or complete request bodies.

Native Debugging

Use the native IDE when the problem is outside React rendering, such as a native-module crash, camera or permission failure, memory issue, or platform API problem.

  • Android: Use Android Studio’s native debugger and adb logcat. React Native 0.81 supports Android API level 24 and later.

  • iOS: Use Xcode’s debugger, console, and Instruments, including Time Profiler and Allocations. React Native 0.81 supports iOS 15.1 and later.

Test gestures, permissions, sensors, backgrounding, and performance on physical devices because simulators and emulators cannot reproduce every device-specific behavior.

Error Boundaries and Crash Reporting

An error boundary is a React class component that catches errors thrown while rendering descendants or executing their lifecycle methods. It typically defines static getDerivedStateFromError and componentDidCatch to render fallback UI and record diagnostic information.

Error boundaries do not catch errors in event handlers, asynchronous callbacks, promises, or native code. Handle those cases explicitly with try/catch, rejected-promise handling, and appropriate application-level validation.

For production diagnostics, services such as Sentry or Bugsnag provide React Native SDKs that can collect JavaScript errors, native crashes, stack traces, and contextual breadcrumbs. Platform-specific crash reporters are still useful for issues that occur before JavaScript starts.

Performance, Network, and Legacy Tooling

Use React Native DevTools’ performance and network tooling for supported JavaScript requests; native HTTP clients or requests hidden by native modules may require platform-specific proxies or packet inspection. Treat captured production traffic as sensitive and avoid disabling certificate validation outside a controlled development environment.

Flipper was deprecated in React Native 0.73, and its React Native integration was removed from the framework in 0.76. Do not add react-native-flipper to new projects. Likewise, the legacy “Remote JS Debugging” Chrome workflow was deprecated in 0.73 and removed in 0.76. With Hermes, use Metro and React Native DevTools for JavaScript debugging, then switch to Android Studio or Xcode when native debugging is required.

39

Which profiling tools would you choose to investigate performance bottlenecks in a React Native application, and when would you use each one?

I would start with React Native DevTools for JavaScript and React rendering issues, then use Android Studio Profiler, Perfetto, or Xcode Instruments when the bottleneck is in native code, rendering, memory, or the operating system. I would not use the legacy Flipper profiler because it has been sunset and replaced by React Native DevTools.

Read full answer

Applies toReact Native 0.81 with the New Architecture and Hermes; Android 7.0 (API 24) and iOS 15.1 minimums

Recommended Toolset

Tool Primary platform Best used to investigate
React Native DevTools Android and iOS React commits, expensive renders, JavaScript CPU activity, Hermes samples, and performance events
React DevTools Profiler Android and iOS Which components caused a render, render duration, and commit activity
React Native Performance Monitor Android and iOS, where exposed by the development menu Quick feedback on FPS, frame timing, CPU usage, and memory usage
Android Studio Profiler Android Native CPU usage, allocations, memory, network activity, and energy consumption
Perfetto Android System-wide timelines, scheduling, I/O, graphics, frame production, and cross-process behavior
Xcode Instruments iOS Native CPU usage, allocations, UI rendering, energy, file and network I/O, and Swift/Objective-C or C++ code

React Native and Hermes Profiling

React Native DevTools is the default first tool for JavaScript-level performance work. Its Performance panel records JavaScript and runtime events, while the Profiler identifies components that rendered unnecessarily or took significant time to commit.

Use it when symptoms include:

  • Slow state updates or excessive component re-renders.

  • Long JavaScript tasks and delayed interactions.

  • Expensive rendering caused by unstable props, context, or selectors.

  • Scheduling problems around animations, navigation, or list updates.

The New Architecture changes how native and JavaScript work is coordinated, but it does not replace profiling. Measure the application rather than assuming a particular architecture boundary is responsible.

Native Platform Profiling

On Android, begin with Android Studio Profiler for a focused view of CPU, memory, and native activity. Use Perfetto when the problem involves the complete system timeline—for example, blocked UI threads, disk I/O, Binder activity, graphics scheduling, or contention between processes. Perfetto system tracing on Android requires Android 9 (API 28) or newer.

On iOS, use Xcode Instruments. Time Profiler is appropriate for native CPU hotspots, while Allocations, the Animation Haptics template, and related instruments help investigate memory, rendering, and interaction costs. Results from a simulator can differ significantly from a physical device, so validate important findings on real hardware.

In an Expo project, use the development-client workflow, but the underlying JavaScript profiler remains React Native DevTools.

Practical Workflow

  1. Reproduce the problem with a deterministic test scenario and record the interaction that triggers it.

  2. Use React Native DevTools and the Profiler to determine whether the main cost is in React rendering or JavaScript execution.

  3. If the trace points to native work, switch to Android Studio Profiler, Perfetto, or Xcode Instruments.

  4. Compare a debug build with an optimized release build, because development mode adds JavaScript and framework overhead.

  5. Measure on a representative physical device with production-like data; a low-powered device often exposes problems hidden by a simulator or flagship handset.

  6. Fix one bottleneck at a time and compare recordings rather than relying only on aggregate FPS.

Common mistakes include relying only on debug-build measurements, treating an FPS counter as proof of a specific cause, and profiling the simulator when the issue involves thermal behavior, native rendering, or real network conditions. Also avoid the archived why-did-you-render package for new work; React DevTools’ Profiler provides the relevant render information without relying on that unmaintained library.

40

How can route parameters be sent from one screen to another with React Navigation?

React Navigation passes data between screens through route parameters. The source screen supplies them when calling navigate, and the destination reads them from route.params; this behavior is the same on Android and iOS.

Read full answer

Applies toReact Navigation 7.x on React Native’s current stable release with the New Architecture and Hermes; Expo’s current stable SDK

How It Works

Each registered screen has a route name. When navigation is triggered, include any required data in the second argument to navigate, such as navigation.navigate('Details', { itemId: 42 }). The destination screen then receives that object through its route prop and reads values from route.params.

React Navigation stores route parameters in its navigation state. They are intended for small values such as identifiers, filters, or display options—not as a replacement for application state or a database.

Installing the Dependencies

For a standard React Native project using the native stack navigator, run npm install @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context.

For an Expo project, run npx expo install @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context. Using expo install selects package versions compatible with the installed Expo SDK.

Type-Safe Parameters

TypeScript can describe every screen’s parameters through a parameter list. This prevents navigation calls from supplying missing or incorrectly typed values.

type RootStackParamList = {
  Home: undefined;
  Details: {
    itemId: number;
    title?: string;
  };
};

With this definition, navigating to Details requires itemId, while title remains optional. For larger applications, define separate parameter types for each stack or navigator so route contracts remain explicit.

Updating and Initial Parameters

  • Update existing parameters: navigation.setParams({ title: 'Updated title' }) merges the supplied values into the current route’s parameters.

  • Set default parameters: initialParams on a stack screen supplies values when that screen is reached without explicit parameters.

  • Push another instance: navigation.push('Details', { itemId: 43 }) can be used when multiple instances of the same screen should exist on the stack.

  • Validate deep-link values: A screen may be entered directly through a deep link. Align its parameter type with the linkable route configuration and validate required values at runtime.

Code Example

import { Button, Text, View } from 'react-native';
import {
  NavigationContainer,
  type RouteProp,
  useNavigation,
  useRoute,
} from '@react-navigation/native';
import {
  createNativeStackNavigator,
  type NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import {
  SafeAreaProvider,
  SafeAreaView,
} from 'react-native-safe-area-context';

type RootStackParamList = {
  Home: undefined;
  Details: {
    itemId: number;
    title?: string;
  };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

function HomeScreen() {
  const navigation =
    useNavigation<NativeStackNavigationProp<RootStackParamList>>();

  return (
    <SafeAreaView>
      <View>
        <Text>Home</Text>
        <Button
          title="View details"
          onPress={() =>
            navigation.navigate('Details', {
              // These values become route.params on the Details screen.
              itemId: 42,
              title: 'Order 42',
            })
          }
        />
      </View>
    </SafeAreaView>
  );
}

function DetailsScreen() {
  const route = useRoute<RouteProp<RootStackParamList, 'Details'>>();

  // Read the route parameter supplied by the previous screen.
  const itemId = route.params.itemId;

  return (
    <SafeAreaView>
      <Text>Item ID: {itemId}</Text>
    </SafeAreaView>
  );
}

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Stack.Navigator initialRouteName="Home">
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen
            name="Details"
            component={DetailsScreen}
            options={({ route }) => ({
              title: route.params.title ?? 'Details',
            })}
          />
        </Stack.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

Best Practices and Common Pitfalls

  • Keep parameters serializable: strings, numbers, booleans, arrays, and plain objects are suitable. Avoid class instances, functions, database connections, or large object graphs.

  • Pass an identifier and load the full record from the appropriate state layer or API. This keeps navigation state small and avoids stale copied data.

  • Do not treat route parameters as durable storage. React Navigation can restore navigation state, but parameter persistence is not a substitute for saved application data.

  • Define parameter types centrally and avoid using any.

  • Validate identifiers before using them in requests, and encode them appropriately when constructing a URL.

  • Prefer Expo for new React Native projects. If using Expo Router, its file-based routing APIs provide their own parameter conventions even though React Navigation powers the underlying navigation stack.

41

How does tab-based navigation work in React Native using the current React Navigation APIs?

Tab navigation lets users switch between top-level sections of an application while preserving each section’s position and state. In React Native, this is commonly implemented with bottom tabs from React Navigation.

Read full answer

Applies toReact Native 0.81.4 with New Architecture and Hermes; React Navigation 7; Expo SDK 54

How It Works

A tab navigator defines a set of routes, each associated with a screen component:

  • Tab navigator: Controls the visible tab bar and which route is currently focused.

  • Route: Represents a destination such as Home, Search, or Profile.

  • Screen component: The UI rendered when its route is focused.

  • Navigation object: Provides methods such as navigate, goBack, and reset.

Tabs are normally lazy-loaded, so a screen is mounted when first visited. React Navigation preserves the state of visited tabs by default, allowing a user to leave a form or scroll position intact and return to it later.

The New Architecture and Hermes do not change the navigation model. React Navigation can still use native capabilities through react-native-screens and react-native-safe-area-context.

Core Setup

For a new mobile application, Expo is recommended. Create the project with create-expo-app, then install @react-navigation/native, @react-navigation/bottom-tabs, react-native-screens, and react-native-safe-area-context.

Each tab should represent a major application area rather than an individual field or action. If a tab needs multiple screens, place a native stack navigator inside that tab; React Navigation provides a native-stack package for this purpose.

TypeScript route parameters should be declared with a parameter list. This enables type-safe calls to navigation.navigate and prevents invalid route names or parameter shapes.

Platform Behavior

  • Android: The hardware back button follows the React Navigation hierarchy. With a tab navigator at the root, back behavior may exit the application when no navigable stack remains.

  • iOS: Native-stack screens can support the interactive back gesture. Switching tabs is not itself a back navigation operation.

  • Safe areas: react-native-safe-area-context handles iPhone notches, home indicators, and Android navigation areas. Avoid fixed tab-bar heights unless the application has a specific design reason for them.

  • Minimum platforms for Expo SDK 54: Android 7.0, API level 24, and iOS 15.1. The tab-navigation APIs themselves do not require an additional platform-specific minimum.

Choosing Between Options

  • Bottom tabs: Best for peer sections that users switch between frequently.

  • Native stack tabs: Use when each section contains a linear flow with drill-down screens.

  • Drawer navigation: Better for a smaller number of less frequently used destinations.

  • Expo Router: For new Expo applications, its file-based routing API can provide tabs while integrating with React Navigation. It is particularly useful when deep linking and route discovery are important.

Code Example

import React from 'react';
import { Pressable, Text } from 'react-native';
import {
  NavigationContainer,
  type Theme,
} from '@react-navigation/native';
import {
  createBottomTabNavigator,
  type BottomTabScreenProps,
} from '@react-navigation/bottom-tabs';

type RootTabParamList = {
  Home: undefined;
  Search: { query?: string } | undefined;
  Profile: undefined;
};

const Tab = createBottomTabNavigator<RootTabParamList>();

// React Navigation 7 no longer uses a fonts property in the theme.
// The theme only defines colors and the dark flag.
const navigationTheme: Theme = {
  dark: false,
  colors: {
    primary: '#2563EB',
    background: '#FFFFFF',
    card: '#FFFFFF',
    text: '#111827',
    border: '#E5E7EB',
    notification: '#DC2626',
  },
};

type HomeScreenProps = BottomTabScreenProps<RootTabParamList, 'Home'>;

function HomeScreen({ navigation }: HomeScreenProps) {
  return (
    <Pressable
      accessibilityRole="button"
      onPress={() => navigation.navigate('Search', { query: 'hooks' })}
    >
      <Text>Search for hooks</Text>
    </Pressable>
  );
}

type SearchScreenProps = BottomTabScreenProps<RootTabParamList, 'Search'>;

function SearchScreen({ route }: SearchScreenProps) {
  return (
    <Text>
      Showing results for {route.params?.query ?? 'all topics'}
    </Text>
  );
}

function ProfileScreen() {
  return <Text>Profile</Text>;
}

export default function App() {
  return (
    <NavigationContainer theme={navigationTheme}>
      <Tab.Navigator
        screenOptions={{
          headerShown: false,
          tabBarActiveTintColor: '#2563EB',
          tabBarInactiveTintColor: '#6B7280',
          tabBarHideOnKeyboard: true,
        }}
      >
        <Tab.Screen
          name="Home"
          component={HomeScreen}
          options={{ tabBarLabel: 'Home' }}
        />
        <Tab.Screen
          name="Search"
          component={SearchScreen}
          options={{ tabBarLabel: 'Search' }}
        />
        <Tab.Screen
          name="Profile"
          component={ProfileScreen}
          options={{ tabBarLabel: 'Profile' }}
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

Best Practices and Common Pitfalls

  • Keep tabs focused on top-level destinations; avoid creating a separate tab for every screen.

  • Keep route parameters small and serializable. Store large or sensitive data in application state rather than navigation parameters.

  • Use nested stack navigators for drill-down flows so each tab retains its own history.

  • Provide accessible labels and test navigation with screen readers and keyboard input where applicable.

  • Do not render every tab eagerly unless immediate preloading is necessary; lazy mounting improves startup performance.

  • Use FlatList or SectionList for long content inside a tab, rather than placing the entire dataset in a ScrollView.

42

How does stack navigation differ from modal navigation when designing screen flows in a React Native application?

Stack navigation manages a hierarchy of screens through push-and-pop behavior, while modal navigation presents a focused, temporary flow over the current screen. In React Navigation, a modal is usually not a separate navigation model; it is a route within a stack configured with a modal presentation style.

Read full answer

Applies toReact Native (current stable version with New Architecture and Hermes); React Navigation 6 with `@react-navigation/native-stack`

Core Difference

  • Stack navigation adds a new screen to the top of the navigation stack. The previous screen remains in the stack and can be restored with a back action.

  • Modal navigation presents a route as a temporary layer over the current context. The user normally completes or cancels the task and then returns to the underlying screen.

  • A modal can contain several screens. In that case, it is commonly implemented as its own stack navigator so the modal workflow has an independent history.

These patterns are complementary rather than mutually exclusive. An application can have a main stack and open a modal stack from one of its screens.

Behavioral Comparison

Concern Stack navigation Modal navigation
Primary interaction Navigate deeper and then return Complete or cancel a focused task
Typical transition Native card-style push transition Native modal transition
Previous screen Logically retained in the stack Usually retained behind the modal, depending on platform presentation
Dismissal Back action or gesture Close button, cancel action, hardware back, or swipe where supported
Best suited for Hierarchical application flows Focused tasks such as login, checkout, or media selection

Typical Use Cases

  • Stack navigation: onboarding, product details, settings sections, and multi-level browsing.

  • Modal navigation: signing in, adding a payment method, selecting an image, choosing filters, or confirming an action.

For example, a store might use a stack for browsing products and product details, but open checkout in a modal stack when the user is ready to purchase. The modal communicates that checkout is a temporary task rather than another level of product browsing.

Implementation in React Navigation

@react-navigation/native-stack provides native stack behavior and is generally preferred over the JavaScript-based @react-navigation/stack when native transitions and platform-consistent behavior are the priority.

A stack route can be configured with presentation: 'modal' through its screen options. This changes how the route is presented without introducing a fundamentally different navigator.

The @react-navigation/stack package remains useful when highly customized transitions, gesture handling, or JavaScript-controlled animations are required.

For new projects, Expo is recommended. Expo projects can install React Navigation and its native dependencies through expo install so the packages remain compatible with the Expo SDK.

Platform Differences

  • iOS: A modal may appear as a native sheet-like presentation, depending on the selected presentation option and device behavior. A full-screen modal can be requested when a sheet is inappropriate. Interactive swipe-to-dismiss is available for some presentations, so accidental dismissal should be considered.

  • Android: Modal presentation is generally full-screen. Android hardware back commonly dismisses the modal and returns to the underlying route.

  • Both platforms: Exact transitions and navigation-bar behavior follow the native stack implementation. New Architecture and Hermes do not change the conceptual distinction between stack and modal flows.

Choosing the Right Approach

Use stack navigation when users need to explore nested content or move through a persistent sequence of screens. Use modal navigation when the user should complete a bounded task without losing the context of the screen underneath.

Common pitfalls include treating every screen as modal, placing unrelated workflows inside one modal stack, and relying on a swipe gesture as the only way to dismiss content. Always provide an explicit close or cancel action where users may have significant unsaved input, and avoid stacking modals on top of modals unless the interaction genuinely requires that depth.

43

How would you describe Expo and its advantages and disadvantages for React Native development?

Expo is a framework and platform built on top of React Native that provides a managed development workflow, a curated set of native modules, file-based routing via Expo Router, and cloud tooling through EAS for building, updating, and submitting apps. It handles most native configuration so teams can ship iOS and Android apps primarily from JavaScript or TypeScript.

Read full answer

Applies toExpo SDK 55 (React Native 0.81+)

What Expo Provides

  • Expo SDK: A maintained collection of native modules for camera, location, notifications, file system, and more, with consistent JavaScript APIs across iOS and Android.

  • Expo Router: A file-based routing system built on React Navigation that supports deep linking, native stacks, and layouts without manual navigation configuration.

  • Expo CLI and EAS: The expo CLI creates and serves projects; EAS Build, Submit, and Update handle cloud binaries, store submission, and over-the-air JavaScript updates.

  • Expo Go and development builds: Expo Go runs most SDK modules instantly on a device; a custom development build is used when a config plugin or native dependency requires it.

Managed Workflow Compared with Bare React Native

Aspect Expo Managed Bare React Native
Native code access SDK modules plus config plugins; npx expo prebuild generates native projects Full direct access to Xcode and Android Studio projects
Build process Cloud builds via EAS or local EAS builds Local Xcode/Android Studio required
Iteration speed Fast with Expo Go or dev client; native changes need a new build Rebuild required for any native change
Setup One command; no native IDE needed for initial work Requires Xcode, Android Studio, JDK, and CocoaPods
Architecture New Architecture and Hermes enabled by default Must be enabled and migrated manually

Advantages

  • Faster onboarding: A new developer can run an app on a physical device in minutes without installing Xcode or Android Studio.

  • Battle-tested modules: SDK modules are updated together, reducing platform-specific bugs and native dependency conflicts.

  • EAS Build and Submit: Cloud iOS builds remove the need for macOS hardware; credentials and submission are managed centrally.

  • EAS Update: JavaScript and asset updates can reach users without a full app store release, subject to Apple’s rules against introducing new features.

  • Config plugins and prebuild: Native customization is declarative, and npx expo prebuild lets you eject to a bare project while retaining Expo tooling.

  • Modern defaults: The New Architecture and Hermes are enabled by default, so apps get improved performance and type safety without manual migration.

Limitations

  • App size: The managed SDK bundle is larger than a carefully pruned bare app, typically adding roughly 15–30 MB to the initial download.

  • Native module constraints: Libraries without a config plugin may require a custom plugin or a development build; not every community module is immediately compatible.

  • Build costs: EAS has a free tier, but high-volume or concurrent builds require a paid plan; self-hosted EAS adds infrastructure overhead.

  • Expo Go limits: Expo Go cannot run arbitrary native modules, so teams using many custom plugins must distribute a custom dev client.

  • Apple OTA restrictions: Over-the-air updates cannot introduce new features or new API usage on iOS; they are intended for bug fixes and minor improvements.

When to Choose Expo

Choose Expo for most new React Native projects, especially when the app fits the SDK, the team wants to minimize native setup, or it needs simplified builds and OTA updates. Use bare React Native only when you require deep native customization without config plugins, need the smallest possible bundle, or have build constraints EAS cannot meet. The npx expo prebuild path offers a practical middle ground.

44

Explain your strategy for integrating a component toolkit such as React Native Elements to improve the user interface of a React Native application.

Integrating a component library like React Native Elements accelerates UI development by providing pre-styled, cross-platform widgets that adhere to design system standards while allowing deep customization through props and theming. You install the package, wrap your app in a theme provider if needed, and replace generic Views with specialized components such as Button, Input, or Card, ensuring consistent look and feel across iOS and Android.

Read full answer

Applies toReact Native 0.76+ with New Architecture, React Native Elements 4.x, Expo SDK 52+

Installation and Configuration

Start by initializing a project with Expo, which simplifies native dependency management under the New Architecture. Install the library and its peer dependencies using npm or yarn. - Expo Managed Workflow: Run npx expo install react-native-elements. This ensures version compatibility with the Expo SDK. - Icons: React Native Elements uses react-native-vector-icons internally; in Expo, configure the Icon component with type="ionicon" or use @expo/vector-icons through the icon prop object syntax. - Configuration: No manual linking is required for Expo projects; for CLI projects, ensure autolinking is enabled in settings.gradle and Podfile.

Using Components in Function Components

Adopt function components with hooks for state management. Import specific components rather than the entire library to reduce bundle size. - State Handling: Use useState for form inputs controlled by React Native Elements Input components. - Event Handlers: Pass onPress callbacks directly to button components. - Composition: Wrap components in standard View containers to manage layout flexbox properties separately from component styling.

Theming and Brand Consistency

Utilize the ThemeProvider at your app root to inject a global style object. This overrides default colors, fonts, and spacing across all child components. - Custom Theme: Define a theme object containing platform, colors, and components keys. - Overrides: Pass buttonStyle or containerStyle props to individual instances for one-off deviations without altering the global theme. - Dark Mode: Combine with useColorScheme from react-native to dynamically switch themes based on system settings.

Platform Considerations and Alternatives

While React Native Elements works on both platforms, visual rendering may differ slightly due to native font rendering or touch feedback. - iOS vs Android: Test touch opacity and icon scaling on both; iOS often requires larger touch targets. - Modern Alternatives: For new projects, consider utility-first libraries like NativeWind or component libraries built for the New Architecture like Tamagui, as React Native Elements development has slowed. - Performance: Avoid nesting too many styled wrappers; use FlatList for scrollable content instead of ScrollView within component lists.

Code Example

import { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Button, Input, ThemeProvider, Icon } from 'react-native-elements';

const theme = {
  Button: {
    raised: true,
  },
};

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <LoginScreen />
    </ThemeProvider>
  );
}

function LoginScreen() {
  const [email, setEmail] = useState('');

  const handleLogin = () => {
    console.log(`Logging in with ${email}`);
  };

  return (
    <View style={styles.container}>
      <Input
        placeholder="Enter email"
        value={email}
        onChangeText={setEmail}
        leftIcon={{ type: 'ionicon', name: 'mail', color: 'gray' }}
      />
      <Button
        title="Sign In"
        onPress={handleLogin}
        icon={{ type: 'ionicon', name: 'arrow-forward', color: 'white' }}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', padding: 20 },
});

Best Practices and Pitfalls

  • Bundle Size: Import only needed components (import { Button } from 'react-native-elements') to support tree-shaking.

  • Customization Limits: If a component requires heavy modification, it may be cleaner to build a custom wrapper than to override numerous internal styles.

  • Accessibility: Verify that generated components meet accessibility standards; add accessibilityLabel props where necessary.

  • Dependency Hygiene: Regularly check for updates, as community libraries may lag behind React Native core API changes.

45

Can you describe the Flipper debugging platform and explain how it benefits React Native development workflows?

Flipper is an extensible desktop debugging platform that was historically the standard tool for inspecting React Native applications, allowing developers to monitor network traffic, logs, and layout hierarchies, though it is now officially deprecated in favor of newer built-in tooling. It operated by establishing a secure connection between the desktop app and the mobile bundle via a plugin architecture that extended native debugging capabilities.

Read full answer

Applies toReact Native 0.74+ (with New Architecture and Hermes), noting Flipper deprecation

What is Flipper

Flipper served as a bridge between the developer’s desktop and the mobile application running on an emulator or physical device. It utilized a client-server model where the React Native app acted as a client sending diagnostic data to the Flipper desktop server. This allowed introspection into the JavaScript bridge (or TurboModules in the New Architecture), native modules, and UI hierarchy without requiring code changes beyond initial setup.

Historical Benefits

Before deprecation, Flipper offered several critical capabilities for React Native development: - Network Inspection: Intercepted HTTP/HTTPS requests to view headers, payloads, and timing, similar to browser DevTools. - Layout Inspector: Visualized the native view hierarchy on Android and iOS, helping diagnose overlapping views or styling issues. - Log Aggregation: Collected console logs, warnings, and errors from the device in a searchable desktop interface. - Plugin Ecosystem: Supported community plugins for state management libraries like Redux or Apollo Client.

Deprecation and Modern Alternatives

As of React Native 0.74, Meta announced the deprecation of Flipper due to maintenance burdens and compatibility issues with the New Architecture. Developers should transition to the following: - React Native DevTools: A Chrome extension that provides component inspection and profiling similar to React DOM DevTools. - Hermes Debugger: Integrated debugging for Hermes engine via Chrome DevTools or VS Code extensions. - Native Logging: Using console.log alongside platform-specific loggers like adb logcat for Android and Console.app for iOS.

Platform Specifics

Behavior historically differed between operating systems during Flipper’s active support: - Android: Required specific Gradle dependencies and often worked more seamlessly due to open debugging ports. - iOS: Required CocoaPods integration and sometimes faced stricter sandboxing limitations regarding network interception. - Expo: Managed Expo projects historically limited Flipper usage unless a custom development client was configured, whereas bare React Native projects had native integration.

Best Practices

  • Migrate Away: Do not add Flipper dependencies to new projects; configure React Native DevTools instead.

  • Verify Configuration: If maintaining legacy code, ensure Flipper is disabled in release builds to prevent security leaks.

  • Use Hermes: Leverage Hermes engine debugging features which are now the recommended path for performance profiling.

  • Check Documentation: Always verify tooling compatibility against the specific React Native version, as tooling evolves rapidly in this ecosystem.

46

Describe how Turbo Modules are used in React Native and what purpose they serve.

Turbo Modules are React Native’s modern native-module system in the New Architecture, replacing legacy native modules with lazy initialization, direct JSI-based calls, and Codegen-generated type-safe interfaces. They cut startup cost by instantiating native modules only when JavaScript first needs them and support both asynchronous promise-based and synchronous native methods.

Read full answer

Applies toReact Native 0.76+ with New Architecture enabled by default and Hermes

How Turbo Modules Work

  • Spec-first generation: You write a TypeScript interface describing the module’s methods and types (Flow specs are deprecated; use TypeScript). Codegen runs at build time and produces C++ JSI bindings, a Java base class for Android, an Objective-C++ base class and protocol for iOS, and matching TypeScript types for JavaScript.

  • Lazy resolution: JavaScript retrieves a module through TurboModuleRegistry; the runtime instantiates it on first access instead of registering every module at startup.

  • JSI calls: The C++ JavaScript Interface exposes native functions as host objects, so calls bypass the legacy serialized bridge. Async methods return promises; sync methods return values directly but block the JS thread while native code runs.

Key Components

  • Spec file: TypeScript interface declaring method signatures, argument types, and return types.

  • Codegen: Build-time tool that generates native scaffolding and JS types from the spec.

  • JSI: C++ layer enabling direct function invocation between JavaScript and native code.

  • TurboModuleRegistry: JS-side entry point that fetches or instantiates modules on demand.

  • Native implementation: Platform-specific class that extends the generated base class or conforms to the generated protocol.

Advantages Over Legacy Native Modules

Feature Legacy Native Modules Turbo Modules
Loading Eager, all at startup Lazy, on first access
Calls Asynchronous only Synchronous and asynchronous
Type safety Manual runtime validation Codegen-enforced at build time
Bridge dependency Required Bypassed via JSI
Performance JSON serialization overhead Direct C++ calls

Platform Implementation Differences

  • Android: The implementation extends the generated Native<ModuleName>Spec class, which itself extends ReactContextBaseJavaModule, and receives a ReactApplicationContext. JNI connects this Java layer to the C++ JSI layer.

  • iOS: The implementation conforms to the generated protocol and subclasses the generated Objective-C++ base class. Swift implementations require an Objective-C++ shim because JSI bindings are C++.

Code Example

// NativeCalculator.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  add(a: number, b: number): Promise<number>;
  multiply(a: number, b: number): Promise<number>;
  getVersion(): string; // synchronous method, no Promise
}

export default TurboModuleRegistry.getEnforcing<Spec>('Calculator');
// App.tsx
import Calculator from './NativeCalculator';

async function calculate() {
  const sum = await Calculator.add(5, 3);
  const version = Calculator.getVersion(); // sync call, blocks until native returns
  console.log(`Sum: ${sum}, Version: ${version}`);
}

Best Practices

  • Define specs in TypeScript: Flow is deprecated; keep specs minimal and accurate so generated code stays small.

  • Generate before writing native code: Run Codegen from Gradle/Xcode first, then implement the generated base class or protocol.

  • Use synchronous methods sparingly: Only for operations that must complete before the next JavaScript statement, such as reading small cached values; never for network or disk I/O.

  • Avoid blocking the JS thread: Long-running sync calls degrade responsiveness; prefer async promises.

  • Handle errors consistently: Catch native exceptions and return rejected promises for async methods; for sync methods, propagate errors through JSI rather than crashing.

  • Migrate incrementally: Legacy and Turbo Modules coexist during migration; prioritize frequently used modules.

47

Explain how native modules are integrated and linked in a modern React Native project using the New Architecture.

Modern React Native relies on Autolinking to connect native dependencies, eliminating the need for manual CLI commands while the New Architecture introduces TurboModules requiring code generation specs.

Read full answer

Applies toReact Native 0.74+ (New Architecture enabled by default)

Autolinking Mechanism

Since React Native 0.60, manual linking via react-native link has been deprecated and subsequently removed. When you install an npm package containing native code (e.g., @react-native-async-storage/async-storage), the build system detects it automatically. - iOS: Dependencies are added to Podfile automatically; running pod install in the ios/ directory links them. - Android: Dependencies are included via Gradle settings files; a standard sync or build triggers the linkage. - Verification: Ensure the package appears in node_modules and has a valid react-native.config.js or package.json react-native field.

New Architecture and TurboModules

With the New Architecture enabled, native modules must be defined as TurboModules to benefit from type safety and performance improvements. This requires a specification file used by CodeGen. - Specs: A TypeScript interface defines the native methods, placed typically in src/specs/. - CodeGen: During build, C++ code is generated from the spec, bridging JavaScript and native platforms uniformly through JSI (JavaScript Interface). - Configuration: The library’s package.json must include a codegenConfig section to specify the type and output path.

Platform-Specific Considerations

Linking behavior differs slightly between operating systems during development. - iOS: Requires CocoaPods. After installing dependencies, navigate to ios/ and execute pod install. Changes to native code often require rebuilding the app from Xcode. - Android: Uses Gradle. Run ./gradlew :app:dependencies to verify resolution. Hermes is the default JS engine, so ensure native modules are compatible with Hermes ABI. - Expo: In the managed workflow, most linking is handled by prebuild. Use config plugins (app.json or app.config.js) to modify native properties without ejecting.

Code Example

Below is an example of a TurboModule specification file that enables autolinking under the New Architecture.

// src/specs/NativeMyModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getString(key: string): Promise<string>;
  setValue(key: string, value: string): Promise<void>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');

Additionally, the library’s package.json requires codegen configuration:

{
  "name": "react-native-my-module",
  "codegenConfig": {
    "name": "MyModuleSpec",
    "type": "modules",
    "jsSrcsDir": "src/specs"
  }
}

Best Practices and Common Pitfalls

  • Prefer Community Packages: For functionality like storage or networking, use maintained libraries like @react-native-async-storage/async-storage instead of writing native code unless necessary.

  • Avoid Manual Linking: Do not edit MainApplication.java or AppDelegate.mm manually unless the library documentation explicitly instructs you to (rare in modern versions).

  • Clean Builds: If linking fails, clean the build folders (ios/build, android/app/build) and reinstall pods.

  • Version Compatibility: Ensure the native module version matches your React Native version, especially during the transition to the New Architecture.

  • Expo Recommendation: For new projects, start with Expo to minimize native configuration overhead; eject only when specific native capabilities are required.

48

What purpose does the Reanimated library serve in React Native development, and what scenarios call for using it?

Reanimated is a React Native library that provides a declarative, high-performance animation system by executing animation logic on the native UI thread through the JavaScript Interface (JSI), rather than on the JavaScript thread. It keeps animations smooth and gesture-responsive even when the JavaScript thread is blocked, making it the preferred choice for gesture-driven interfaces and complex UI transitions.

Read full answer

Applies toReact Native 0.76+ with Reanimated 3.x and the New Architecture (Hermes)

How It Works

  • UI Thread Execution: Reanimated serializes small JavaScript functions called worklets and runs them directly on the UI thread, so frame calculations do not wait for the JavaScript thread or the bridge.

  • Shared Values: Values created with useSharedValue can be read and written from both JavaScript and the UI thread; changes trigger reactive updates without serialization overhead.

  • Worklets: Functions prefixed with the 'worklet' directive are captured, transformed by the Reanimated Babel plugin, and executed on the UI thread at runtime.

  • JSI Integration: Under the New Architecture, JSI allows synchronous, memory-efficient communication between threads, replacing the asynchronous bridge used by the legacy Animated API.

Key Features

  • Declarative Animations: Animate by describing how shared values map to styles, using functions such as withSpring, withTiming, withSequence, and withRepeat.

  • Gesture Integration: Works with react-native-gesture-handler to bind gestures directly to shared values, producing 60 fps drag, pinch, and rotation responses.

  • Layout Animations: Views can animate when they mount, unmount, or change position using Reanimated’s layout animation API, reducing the need for manual layout measurement.

  • Shared Element Transitions: Reanimated 3 supports shared element transitions between screens, preserving visual continuity during navigation.

  • Physics-Based Motion: Spring configurations and easing curves create natural-feeling motion without manual interpolation.

When to Use Reanimated

Scenario Why Reanimated Helps
Drag-and-drop or reordering Gesture updates run on the UI thread, so dragging stays smooth under load
Complex screen transitions Shared element and layout animations keep navigation fluid
Performance-critical UI Animations continue while the JavaScript thread handles data or state
Interactive controls Sliders, swipeable cards, and pull-to-refresh feel immediate
Micro-interactions Button feedback, loaders, and small motions have low overhead

For simple one-off fades, opacity changes, or basic transitions, React Native’s built-in Animated API or LayoutAnimation is often sufficient. Choose Reanimated when animations must stay synchronized with gestures, survive JavaScript thread contention, or span multiple layout changes.

Best Practices

  • Install both packages: Pair react-native-reanimated with react-native-gesture-handler for gesture-driven animations.

  • Configure the Babel plugin: Add 'react-native-reanimated/plugin' as the last entry in the plugins array of babel.config.js; otherwise worklets are not transformed correctly.

  • Keep worklets lightweight: Worklets run on the UI thread, so avoid heavy computation, closures over large objects, or non-serializable values inside them.

  • Prefer Expo for new projects: Expo auto-configures Reanimated and its native dependencies, reducing setup friction.

  • Animate transform and opacity: Avoid animating layout properties such as width, height, margin, or top, which trigger expensive layout passes.

  • Test on low-end devices: The performance gap between Reanimated and the JavaScript-thread animation system is most noticeable on slower hardware.

49

Can you explain what Fast Refresh is in React Native and how it functions during development?

Fast Refresh is the live editing feature in React Native that preserves component state while updating code instantly during development. It replaces the older Hot Reloading mechanism, offering a more reliable and faster feedback loop without requiring a full app reload for most changes.

Read full answer

Applies toReact Native 0.61 and later (including current stable releases with New Architecture and Hermes)

Core Mechanism

Fast Refresh operates through the Metro bundler, which detects file changes and sends updates to the running application. Instead of reloading the entire JavaScript bundle, it injects the new module code and reconciles the component tree using React’s internal reconciliation logic. This process ensures that local state managed via useState or useEffect remains intact across edits, allowing developers to tweak UI or logic without losing their place in the app flow. It is enabled by default in development builds and requires no configuration when using Expo or the standard CLI template. The feature works with both Hermes and JavaScriptCore; Hermes can reduce reload times but is not required for Fast Refresh to function.

Comparison with Hot Reloading

Prior to Fast Refresh, React Native relied on Hot Reloading, which often failed to preserve state correctly or triggered full reloads unexpectedly. Fast Refresh improves upon this by limiting the scope of updates to individual components rather than the whole module tree. While Hot Reloading attempted to patch the runtime globally, Fast Refresh treats components as discrete units, preserving internal state while updating the component’s implementation. This distinction reduces the occurrence of red screen errors caused by inconsistent state shapes during development. Consequently, developers experience fewer interruptions when iterating on UI designs or fixing logic bugs within specific components.

Limitations and Fallbacks

Not all changes trigger a fast refresh. Modifications to class components typically force a full reload because Fast Refresh is optimized for function components and hooks. Similarly, changes to native modules, configuration files like app.json or build.gradle, or the dependency structure usually require a rebuild. On iOS, certain changes to native code necessitate rebuilding via Xcode, whereas Android may require a Gradle sync. If the app encounters a syntax error, Fast Refresh displays an overlay with the error details, allowing fixes without crashing the entire session. Developers should note that adding or removing hooks can sometimes cause state mismatches, prompting a manual reload to ensure consistency.

Error Handling Behavior

When a syntax error or runtime exception occurs during a refresh cycle, React Native displays a full-screen red box or an overlay depending on the platform. This overlay provides the stack trace and the specific file location of the error, enabling quick diagnosis. Unlike a full crash, the application remains alive in the background, and correcting the file triggers another refresh attempt automatically. On Android, the developer menu can be accessed via shaking the device or using adb commands, while iOS uses a shake gesture or the simulator’s Device menu. This persistent connection allows for rapid iteration even when introducing temporary bugs during experimentation.

Best Practices for Development

To maximize the benefits of Fast Refresh, developers should adhere to a few guidelines. First, prioritize function components over class components, as the latter do not support state preservation during updates. Second, avoid placing heavy side effects or subscriptions directly in the render path; instead, manage them within useEffect hooks to prevent unintended re-executions during refresh cycles. When using Expo, ensure the development client is running the latest version to maintain compatibility with the underlying Metro bundler features. Finally, if Fast Refresh behaves unexpectedly, clearing the Metro cache (npx react-native start --reset-cache) often resolves stale module references. Keeping dependencies updated ensures compatibility with the latest engine optimizations used by the refresh system.

50

What is the process by which React Native transforms JavaScript into native mobile applications?

React Native does not compile JavaScript to native code in the traditional sense. Instead, it runs JavaScript in a JavaScript engine (Hermes) that communicates with native platform code through a bridge or, in the New Architecture, through JSI (JavaScript Interface). The native UI components are rendered using actual platform APIs, not web views.

Read full answer

Applies toReact Native 0.76+ with New Architecture enabled and Hermes engine

How React Native Works

React Native uses a different approach than cross-platform frameworks that compile to native binaries:

  • JavaScript execution: Your React code runs in Hermes, a JavaScript engine optimized for React Native. Hermes precompiles JavaScript to bytecode, improving startup time.

  • Native module communication: JavaScript communicates with native platform code (Java/Kotlin on Android, Objective-C/Swift on iOS) through JSI, which allows direct, synchronous calls without serialization overhead.

  • Native UI rendering: React Native maps React components to actual platform UI components. A <View> becomes UIView on iOS and android.view.View on Android.

The New Architecture

The New Architecture (enabled by default in React Native 0.76+) replaces the old asynchronous bridge with more efficient systems:

Component Purpose Benefit
JSI (JavaScript Interface) Direct C++ binding between JS and native Synchronous calls, no serialization
Fabric New rendering system Concurrent rendering, better performance
TurboModules Lazy-loaded native modules Faster startup, on-demand loading
Codegen Type-safe native module interfaces Compile-time validation

The Rendering Pipeline

  1. React renders a component tree in JavaScript

  2. Shadow tree is created in C++ to calculate layout (Yoga engine)

  3. Fabric creates the native UI tree from the shadow tree

  4. Native platform renders actual UI components on screen

Key Differences from Other Approaches

  • Not a web view: Unlike Cordova or Ionic, React Native renders native UI components, not HTML in a browser

  • Not compiled code: Unlike Flutter (Dart compiled to native) or native apps, JavaScript runs in an engine at runtime

  • Hot reloading possible: Since JavaScript isn’t compiled ahead-of-time, developers can see changes instantly during development

Platform-Specific Considerations

  • Android: Requires minimum API 23 (Android 6.0). Hermes bytecode is bundled in the APK.

  • iOS: Requires minimum iOS 15.1. Hermes runs as a framework embedded in the app bundle.

  • Bundle size: Apps include the Hermes engine (~3-4 MB on Android, similar on iOS), adding to app size compared to fully native apps.

Performance Implications

The New Architecture significantly improves performance over the legacy bridge:

  • JSI eliminates JSON serialization overhead for native calls

  • Fabric enables concurrent rendering and prioritized updates

  • TurboModules load only when needed, reducing memory footprint

  • Layout calculation happens in C++ (Yoga), shared across platforms

51

What are the best practices for managing and storing sensitive data in a React Native application?

Handling sensitive data in React Native requires using platform-specific secure storage, avoiding plaintext persistence, and following security best practices like minimizing exposure and clearing data on app background. The recommended approach is to use expo-secure-store for Expo projects or react-native-keychain for bare React Native, which leverage iOS Keychain and Android EncryptedSharedPreferences/Keystore.

Read full answer

Applies toReact Native 0.76+ with New Architecture, Expo SDK 52+ (or bare workflow with react-native-keychain)

Platform-Specific Secure Storage

  • iOS: The Keychain Services API stores data in an encrypted hardware-backed container. expo-secure-store and react-native-keychain both wrap this. By default, Keychain items are removed when the app is uninstalled; they can persist across reinstalls only if stored in a shared access group that is not tied to the app’s bundle.

  • Android: Uses the Android Keystore system with EncryptedSharedPreferences (on API 23+) or direct encrypt/decrypt with a key stored in the Keystore. Both libraries handle this transparently. For API 28+ the StrongBox-backed key store is available.

Both libraries automatically encrypt data before writing and decrypt on read. Never store tokens, passwords, or API keys in AsyncStorage, localStorage (WebView), or file system plaintext.

Avoid Deprecated Storage

  • AsyncStorage was removed from React Native core; the community package @react-native-async-storage/async-storage stores data as plaintext in an unencrypted SQLite database (iOS) or a shared preferences XML file (Android). It is suitable for non‑sensitive user preferences only.

  • For sensitive values, always use expo-secure-store or react-native-keychain. If you need both sensitive and non‑sensitive storage, use the secure library for secrets and AsyncStorage for UI‑only state.

Environment Variables and Secrets

  • Client‑side code cannot hide secrets from an attacker who decompiles the bundle. .env files (with react-native-dotenv or expo-constants) are bundled as plaintext. Never store API keys, signing secrets, or tokens in environment variables.

  • Instead, proxy through your own backend: the app sends a user‑authenticated request to your server, which holds the secret and makes the external API call. Only short‑lived session tokens should live on the device.

Best Practices for In‑Memory and Network Security

  • Clear data on app background: Listen to AppState changes; when moving to background, purge sensitive in‑memory data (e.g., zero out buffers). Libraries like react-native-keychain allow you to invalidate in‑memory credentials on demand.

  • Avoid logging: Never console.log sensitive strings. Use a logging wrapper that filters secrets in release builds.

  • Use HTTPS with certificate pinning: For API calls, use a library such as react-native-ssl-pinning or a custom fetch implementation that validates certificates to prevent man‑in‑the‑middle attacks.

  • Minimize data in the app bundle: Do not embed test credentials or hard‑coded tokens. Use a build‑time code scanner (e.g., a static analysis tool) to detect hard‑coded secrets.

Common Pitfalls

  • Storing tokens in Redux or AsyncStorage: These are not encrypted. Attackers with physical access or malware can read them.

  • Using expo-secure-store on Android without requiring authentication: The default accessibility is WHEN_UNLOCKED, which does not enforce biometric or device credential prompts. For high‑security tokens, consider a library that supports requiring authentication on each access (e.g., react-native-keychain with its authentication options).

  • Forgetting to clear data on app uninstall: By default, Keychain items are removed when the app is uninstalled; however, if you use a shared access group they may persist. Android EncryptedSharedPreferences are removed on uninstall.

53

Explain why accessibility matters in React Native applications and what practices developers should follow.

Accessibility in React Native ensures that users with disabilities—whether visual, motor, auditory, or cognitive—can perceive, navigate, and interact with your mobile app. Beyond ethical responsibility and legal compliance in many jurisdictions, accessible apps reach a broader audience and often deliver better user experiences for everyone. For new projects, Expo is recommended because it simplifies accessibility testing and configuration across platforms.

Read full answer

Applies toReact Native 0.76 with New Architecture, Android 5.0+ (API 21), iOS 13+

Core Accessibility Props

React Native provides a set of cross-platform accessibility props that map to native accessibility APIs on both platforms:

  • accessible: When true, marks the element as an accessible unit that screen readers can focus on as a single element.

  • accessibilityLabel: A string that screen readers announce when users focus on the element; essential for icons and images without visible text.

  • accessibilityRole: Describes the element’s purpose (button, link, header, image, text, keyboardkey, search, adjustable, imagebutton, summary, alert, checkbox, combobox, menu, menubar, menuitem, progressbar, radio, radiogroup, scrollbar, spinbutton, switch, tab, tablist, timer, toolbar).

  • accessibilityHint: Optional additional context explaining what happens when the user activates the element.

  • accessibilityState: Describes the current state (selected, disabled, checked, busy, expanded).

  • accessibilityValue: Represents the current value for elements like sliders (min, max, now, text).

Platform-Specific Considerations

Accessibility behavior differs between Android and iOS:

Aspect Android iOS
Screen reader TalkBack VoiceOver
Focus navigation Linear and spatial gestures Rotor gestures and swipe navigation
Announcements Uses accessibilityLiveRegion Uses accessibilityLiveRegion with different values
Font scaling Respects system font size settings Respects Dynamic Type
  • Android: Use importantForAccessibility to control whether elements are visible to TalkBack. The accessibilityActions prop and onAccessibilityAction callback handle custom actions.

  • iOS: Supports accessibilityViewIsModal to contain VoiceOver focus within a modal. The accessibilityIgnoresInvertColors prop prevents color inversion for images and media.

Key Areas to Address

  • Touch targets: Ensure all interactive elements are at least 44×44 points on iOS and 48×48 dp on Android to accommodate motor impairments.

  • Color contrast: Maintain a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text (WCAG AA). Don’t rely on color alone to convey information.

  • Screen reader support: Provide meaningful accessibilityLabel values for all interactive elements and images. Group related elements with accessible={true} on a parent view.

  • Dynamic text: Test with system font scaling enabled. Use scalable units and avoid fixed heights that clip text when enlarged.

  • Focus management: Control focus order logically. Use accessibilityRole="alert" for time-sensitive announcements that need immediate attention.

Common Pitfalls

  • Missing labels on icons: A hamburger menu icon needs accessibilityLabel="Menu" or similar.

  • Invisible elements receiving focus: Use importantForAccessibility="no" on Android or accessibilityElementsHidden={true} on iOS for decorative elements.

  • Custom components without roles: A custom button built from a TouchableOpacity and Text needs accessibilityRole="button" so screen readers announce it correctly.

  • Announcing state changes: Use the announceForAccessibility method on AccessibilityInfo to alert users when content updates dynamically.

54

What architectural patterns and development practices do you implement to keep a React Native codebase maintainable and scalable as it evolves?

Ensuring long-term viability relies on establishing a modular architecture enforced by strict typing, automated testing, and performance-aware patterns from day one. This approach minimizes technical debt and allows teams to scale features without refactoring the entire codebase.

Read full answer

Applies toReact Native 0.76+ (New Architecture), Expo SDK 52+, TypeScript 5.3+

Project Architecture and Folder Structure

Organize code by feature rather than by type to keep related logic colocated. A typical structure groups components, hooks, and styles within a src/features/ directory. This reduces cognitive load when modifying specific functionality. - Feature-based grouping: Each folder contains UI, logic, and tests for one domain. - Shared utilities: Keep generic helpers in src/lib/ to avoid duplication. - Expo modules: Use Expo Router for file-based navigation in new projects, reducing boilerplate configuration compared to manual linking.

State Management and Data Fetching

Avoid global state for local UI concerns. Use server-state libraries to handle caching and synchronization. - Local state: Use useState or useReducer within function components. - Server state: Adopt TanStack Query (@tanstack/react-query) for API data handling. - Global state: Choose Zustand or Context API for truly shared client state, avoiding Redux boilerplate unless necessary. - Storage: For persistent data, use @react-native-async-storage/async-storage instead of the removed core module.

Type Safety, Quality, and Testing

TypeScript is essential for catching errors early in large codebases. Automated tests prevent regressions during scaling. - Strict mode: Enable strict: true in tsconfig.json. - Linting: Use ESLint with @react-native plugins and Prettier for formatting. - Component props: Define interfaces for all component props to ensure contract stability. - Testing: Use Jest for logic and React Native Testing Library for UI behavior; run checks via CI/CD on every pull request.

Performance Optimization

Scalability degrades if performance is ignored. The New Architecture improves throughput significantly. - Lists: Always use FlatList or SectionList for scrollable content; never wrap many items in a ScrollView. - Hermes: Enable the Hermes engine for faster startup and lower memory usage on Android and iOS. - New Architecture: Leverage TurboModules and Fabric for better native interoperability and JSI. - Platform specifics: Check Platform.OS when implementing native-specific features, as Android back button handling differs from iOS gestures.

Sign in to keep reading

You've opened 2 full answers. Sign in to read all 54 React Native answers, mark what you know and pick up where you left off.

All Web and Mobile Development subjects