Could you define React Native and highlight the primary differences between it and React when building user interfaces?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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.
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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.
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.
KnownRevise
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 answerHide 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
Use TypeScript for validation: Define prop interfaces to catch errors at development time rather than runtime.
Name callbacks meaningfully: Callback props should indicate intent (e.g., onSubmit, onPress rather than just action).
Provide sensible defaults: For optional props, use default parameter values instead of conditional rendering for simple cases.
Avoid prop drilling: When deeply nested components need the same data, consider Context API or state management libraries.
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?
KnownRevise
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 answerHide 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
This example demonstrates proper state initialization, TypeScript typing, functional updates, and async handling with loading states suitable for production apps.
9
What is the recommended approach for defining and applying styles in a React Native application?
KnownRevise
The recommended approach for styling React Native components is the StyleSheet API: define style objects with StyleSheet.create() and pass them to the style prop. This keeps styling declarative, allows the framework to compile styles into efficient native references, and works consistently under the New Architecture and Expo.
Read full answerHide full answer
Applies toReact Native 0.76+ (New Architecture enabled by default), Expo SDK 52+
How It Works
StyleSheet.create() registers style definitions and returns an opaque object whose values are style identifiers.
Under the New Architecture, those identifiers are passed to native through JSI instead of the legacy bridge, reducing serialization overhead.
The style prop accepts a single style object, an array of styles, or null/undefined; arrays are merged left to right, with later entries overriding earlier ones.
Styles are resolved at render time, so static sheets can be combined with dynamic values.
position: Supports absolute and relative; absolute positioning is relative to the parent.
Defining and Applying Styles
Define sheets at module scope or inside components with useMemo when they depend on props or state. Apply them directly, for example <View style={styles.card}>, or combine conditions with an array such as <View style={[styles.card, props.highlighted && styles.highlighted]}>. Keep static sheets separate and merge only the computed part when values must change per render. Use StyleSheet.flatten() only when you need the computed object, such as for layout measurement, because it bypasses the opaque reference optimization.
Platform-Specific Styling
Import Platform from 'react-native', not from internal paths.
Use Platform.OS === 'ios' or Platform.select({ ios: ..., android: ..., default: ... }) for style differences.
Use Platform.Version to gate behavior on Android API level or iOS version.
Metro also resolves .android.js and .ios.js file extensions, but Platform.select is usually clearer for small style variations.
Test both platforms because default fonts, shadow rendering, touch feedback, and safe-area behavior differ.
Best Practices
Use StyleSheet.create(): It is the officially recommended factory and is required for New Architecture style compilation.
Keep styles stable: Define styles outside the render path or memoize them; avoid recreating identical objects on every render.
Extract design tokens: Store colors, spacing, radii, and typography in shared constants so themes remain consistent.
Use Dimensions and PixelRatio: For responsive layouts and hairline borders, but prefer flex-based layouts where possible.
Support dark mode: Read useColorScheme() and switch palettes instead of hardcoding light colors.
Style list items consistently: When using FlatList, give item styles stable references to avoid unnecessary re-renders.
Common Pitfalls
Pitfall
Cause
Fix
Style objects recreated on every render
Inline object literals in JSX
Move to StyleSheet.create() or useMemo
Missing layout space
Parent lacks flex: 1 or child lacks flex basis
Explicitly set flex containers and basis
Shadows missing on Android
Using iOS-only shadow* props
Use elevation on Android or a shared shadow component
Inconsistent spacing
Hardcoded pixel values across screens
Use token-based spacing and Dimensions where needed
Importing internal modules
Using paths like react-native/Libraries/Platform
Import public exports from 'react-native'
10
In React Native component development, how do state and props differ regarding data ownership and mutability?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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
Define the context object, ideally with a TypeScript type and an undefined default so a custom hook can detect a missing provider.
Build a provider component that owns the state (usually with useState) and memoizes the value with useMemo.
Export a custom hook that calls useContext and throws when used outside the provider.
Render the provider near the app root (or the subtree that needs the data).
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide full answer
Applies toReact Native 0.80 with the New Architecture and Hermes
How It Works
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.
React renders components: When props, state, or context change, React executes the relevant component functions and creates a new element tree.
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.
Fabric applies native changes: The New Architecture’s Fabric renderer translates those updates into operations for the platform’s native view system:
On iOS, components correspond primarily to UIView subclasses.
On Android, components correspond primarily to Android View subclasses and related native widgets.
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?
KnownRevise
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 answerHide 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.
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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:
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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.
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.
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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.
23
What are the recommended strategies for persisting local state across app restarts in React Native?
KnownRevise
Persistent local state should be selected according to sensitivity and data shape: use AsyncStorage for small non-sensitive values, SecureStore for secrets, and SQLite for larger or queryable data. Keep ephemeral UI state in memory and hydrate persisted state before treating it as application state.
Read full answerHide full answer
Applies toReact Native 0.81 with New Architecture and Hermes; Expo SDK 54; `@react-native-async-storage/async-storage` 2.2.x; `expo-secure-store` 15.x; `expo-sqlite` 16.x; Zustand 5.x
Storage Options
Option
Recommended use
Important behavior
@react-native-async-storage/async-storage
Preferences, feature flags, drafts, and small cached objects
An asynchronous, unencrypted key-value store that accepts string keys and string values. Serialize objects with JSON. Its Android implementation is SQLite-backed, while its iOS implementation is file-based. It is not intended for large datasets or high-frequency writes.
expo-secure-store
Access tokens, refresh tokens, API keys, and other small secrets
Uses iOS Keychain and Android encrypted storage backed by the Android Keystore. It is intended for secrets, not large blobs.
expo-sqlite
Structured records, relational data, search, pagination, and transactional updates
Provides a local SQLite database with an explicit schema, indexes, and migration support. Use it when data must be queried or updated as related records.
Expo SDK 54 is the recommended baseline for new projects because it integrates these native modules with Expo’s managed project configuration.
State Management Integration
Persist only durable state: Keep transient component state, loading indicators, and derived values in memory.
Configure the persistence adapter: Zustand 5.x provides the persist middleware, but it must be given an AsyncStorage adapter explicitly; the middleware does not select a storage backend automatically.
Hydrate before writing: Mark the store as not hydrated, complete the initial read, and prevent persistence writes until then. Otherwise, an initial default value can overwrite previously saved state.
Serialize deterministically: Convert supported values to JSON and validate the decoded shape before using it in application logic.
Control write frequency: Debounce frequent updates and avoid persisting every transient change.
Security and Platform Behavior
Use SecureStore for secrets: AsyncStorage provides no encryption, so do not place passwords, authentication tokens, private API keys, or sensitive personal information there.
Treat custom encryption carefully: Client-side encrypted values in AsyncStorage can be acceptable when they use a maintained cryptographic design and the encryption key is itself protected by SecureStore. Embedding the key in the JavaScript bundle provides little meaningful protection.
Account for device access: Normal application sandboxing limits access to AsyncStorage. Android run-as can expose debuggable application data, while rooted or jailbroken devices weaken isolation on both platforms.
Supported platforms: Expo SDK 54 and React Native 0.81 support Android 7.0 (API 24) and later and iOS 15.1 and later, so SecureStore is available across the supported application targets.
Reliability and Data Lifecycle
Version stored data: Include a schema version in keys or payloads and implement explicit migrations before releasing incompatible changes.
Handle malformed data: Catch deserialization and validation failures, then fall back to safe defaults or remove the invalid entry.
Remove data selectively: Use removeItem for logout or feature cleanup. clear removes every AsyncStorage key for the application and may affect unrelated features.
Batch independent operations: Use multiSet or multiGet to reduce native round trips when manipulating multiple independent keys. Use SQLite transactions when related records require transactional consistency.
Expect restoration behavior: Uninstalling normally removes local application data, but operating-system or cloud backup mechanisms may restore some data after reinstallation or device migration. Do not treat uninstall as a guaranteed secure wipe.
Best Practices and Pitfalls
Avoid large values: AsyncStorage has no documented fixed key/value limit, but large payloads and frequent writes can degrade performance and consume disk space.
Do not use it as the sole source of truth: Persisted state can be missing, stale, corrupt, or restored from a backup.
Avoid sensitive logging: Do not log stored payloads, tokens, decrypted secrets, or full error objects that might contain sensitive values.
Test upgrade and logout flows: Verify migrations, corruption recovery, partial logout, app restarts, reinstalls, and behavior when secure storage is unavailable.
24
How should network requests be made in React Native with either the built-in fetch API or Axios?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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
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
Start with useState or useReducer.
Introduce Context for relatively stable cross-tree values.
Use a dedicated global store when state is shared, frequently updated, or governed by complex business rules.
Use TanStack Query for remote data rather than treating API responses as ordinary global state.
Use React Hook Form for forms and navigation libraries for route state.
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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.
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?
KnownRevise
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 answerHide 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.
32
What is the recommended way to use Linking in React Native to open external URLs and process URLs delivered to the application?
KnownRevise
React Native’s Linking API is the recommended cross-platform way to open external URLs and handle deep links: use Linking.openURL() to launch a URL, Linking.canOpenURL() to check whether the device can handle it, and Linking.getInitialURL() plus the url event to process links delivered to the app. For new projects, Expo (current SDK) is recommended and exposes the same behavior through expo-linking.
Read full answerHide full answer
Applies toReact Native 0.86 with the New Architecture and Hermes; Expo SDK 56 for managed workflows
Core APIs
Linking.openURL(url): Opens the URL with the OS default handler. https: opens in the browser, while mailto:, tel:, sms:, or geo: opens the corresponding app.
Linking.canOpenURL(url): Returns a Promise<boolean> indicating whether the OS appears able to handle the URL. On iOS, custom schemes must be declared in LSApplicationQueriesSchemes, or the method may return false even when openURL() would succeed.
Linking.getInitialURL(): Returns a Promise<string | null> containing the URL that launched the app, or null if the app was not opened from a link.
Linking.addEventListener('url', handler): Receives URLs while the app is already running. The handler receives { url: string }. Remove the subscription in cleanup with subscription.remove().
Platform Configuration
Android custom schemes: Add an android.intent.action.VIEW intent filter with android.intent.category.BROWSABLE and a <data android:scheme="..."> entry to AndroidManifest.xml.
Android universal links: Use an HTTPS intent filter with android:autoVerify="true" and host a valid assetlinks.json file at https://<domain>/.well-known/assetlinks.json.
iOS custom schemes: Register the scheme under CFBundleURLTypes in Info.plist. Add every scheme queried with canOpenURL() to LSApplicationQueriesSchemes.
iOS universal links: Enable the Associated Domains entitlement with applinks:<domain> and host the Apple App Site Association file at https://<domain>/.well-known/apple-app-site-association.
Android 11+ (API 30): If canOpenURL() checks package-specific schemes, declare the target packages in <queries> in AndroidManifest.xml.
React Native 0.86 supports Android 7.0 (API 24) and iOS 15.1 as minimum versions. Linking itself does not require a newer OS API.
Handling Incoming URLs
Use getInitialURL() for cold-start links and the url event for links delivered while the app is active. Both paths should share the same validation and routing logic.
Treat every incoming URL as untrusted input. Validate the scheme, host, and path before navigating internally, opening a browser, or using parameters. An allowlist of schemes alone is insufficient because another app may register the same scheme.
Code Example
import React, { useCallback, useEffect } from 'react';
import {
Alert,
Button,
Linking,
} from 'react-native';
const EXTERNAL_PROTOCOLS = new Set(['https:', 'mailto:', 'tel:']);
const INCOMING_PROTOCOLS = new Set(['https:']);
function parseAllowedUrl(
rawUrl: string,
allowedProtocols: Set<string>,
): URL | null {
try {
const parsed = new URL(rawUrl);
if (!allowedProtocols.has(parsed.protocol)) {
return null;
}
return parsed;
} catch {
return null;
}
}
async function openExternalUrl(rawUrl: string): Promise<void> {
const url = parseAllowedUrl(rawUrl, EXTERNAL_PROTOCOLS);
if (!url) {
Alert.alert('Unsupported URL', 'Only approved URL schemes are allowed.');
return;
}
try {
const supported = await Linking.canOpenURL(url.toString());
if (!supported) {
Alert.alert(
'Unable to open URL',
'No installed application can handle this URL.',
);
return;
}
await Linking.openURL(url.toString());
} catch {
Alert.alert(
'Unable to open URL',
'The operating system could not open the requested resource.',
);
}
}
export function LinkingExample(): React.JSX.Element {
const handleDeepLink = useCallback((url: URL) => {
// Replace this with validated internal navigation.
Alert.alert('Deep link received', `${url.pathname}${url.search}`);
}, []);
useEffect(() => {
let active = true;
const receiveUrl = (rawUrl: string) => {
const url = parseAllowedUrl(rawUrl, INCOMING_PROTOCOLS);
if (url) {
handleDeepLink(url);
}
};
// Handles a cold start caused by an incoming universal link.
void Linking.getInitialURL()
.then((url) => {
if (active && url) {
receiveUrl(url);
}
})
.catch(() => {
// Ignore an unavailable initial URL.
});
// Handles links received while the application is running.
const subscription = Linking.addEventListener('url', ({ url }) => {
receiveUrl(url);
});
return () => {
active = false;
subscription.remove();
};
}, [handleDeepLink]);
return (
<Button
title="Open React Native documentation"
onPress={() => {
void openExternalUrl('https://reactnative.dev/');
}}
/>
);
}
Best Practices and Common Pitfalls
Prefer HTTPS over cleartext HTTP; Android may block cleartext traffic by default, and iOS App Transport Security generally rejects insecure HTTP.
Use canOpenURL() as an optional capability check, not as the sole error handler. Always catch failures from openURL() because the target app can still be unavailable after the check.
Keep an allowlist of accepted schemes, hosts, and paths. Validate incoming URLs before using them for internal navigation.
Always call subscription.remove() when the component unmounts to avoid retaining stale handlers.
Do not use removed React Native core modules such as AsyncStorage or NetInfo; install their community replacements (@react-native-async-storage/async-storage and @react-native-community/netinfo) when needed.
For new projects, start with Expo (current SDK), which includes expo-linking and preconfigures many universal-link settings.
Test both cold-start and warm-link scenarios on Android and iOS, because intent handling and universal-link behavior differ between platforms.
33
Which techniques can be used to improve the performance of a React Native application?
KnownRevise
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 answerHide 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
Find the largest bottleneck using profiling.
Fix unnecessary rerenders and unbounded list growth first.
Move animation and compute-heavy work off the JavaScript thread.
Reduce startup and network work.
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?
KnownRevise
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 answerHide 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.
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?
KnownRevise
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 answerHide 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.
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?
KnownRevise
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 answerHide 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.
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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
Reproduce the problem with a deterministic test scenario and record the interaction that triggers it.
Use React Native DevTools and the Profiler to determine whether the main cost is in React rendering or JavaScript execution.
If the trace points to native work, switch to Android Studio Profiler, Perfetto, or Xcode Instruments.
Compare a debug build with an optimized release build, because development mode adds JavaScript and framework overhead.
Measure on a representative physical device with production-like data; a low-powered device often exposes problems hidden by a simulator or flagship handset.
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?
KnownRevise
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 answerHide 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.
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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.
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.
KnownRevise
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 answerHide 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.
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?
KnownRevise
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 answerHide 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.
KnownRevise
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 answerHide 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.
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++.
// 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.
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.
KnownRevise
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 answerHide 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.jsonreact-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.
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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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?
KnownRevise
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 answerHide 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
React renders a component tree in JavaScript
Shadow tree is created in C++ to calculate layout (Yoga engine)
Fabric creates the native UI tree from the shadow tree
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?
KnownRevise
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 answerHide 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.
52
What are the recommended patterns for making API calls and fetching data in a React Native app?
KnownRevise
Best practices for API interaction and data fetching in React Native revolve around using a dedicated data‑fetching library like TanStack Query, managing loading and error states consistently, and handling network connectivity gracefully.
Read full answerHide full answer
Applies toReact Native 0.76 (New Architecture, Hermes), Expo SDK 52
How It Works
React Native apps typically fetch data from REST or GraphQL APIs. The fetch lifecycle includes sending a request, waiting for a response, parsing the body, and updating UI state. Without a library, you would manually track loading, error, and data states in useState and useEffect. A library like TanStack Query automates caching, background refetching, and stale‑invalidation, reducing boilerplate and improving performance.
Key Components
TanStack Query (React Query) – The most widely adopted solution for server state management. It provides useQuery for reads, useMutation for writes, and automatic cache management.
Axios or fetch – The HTTP client of your choice. fetch is built into React Native; Axios offers request/response interceptors (useful for attaching tokens).
Expo SecureStore – For storing auth tokens securely (replaces AsyncStorage for secrets).
@react-native-community/netinfo – The community package for detecting network connectivity. Use it to show offline banners or queue requests.
Manual caching, risk of race conditions, no request deduplication
Redux Toolkit Query
Integrated with Redux for global state, similar caching
Heavier setup, often overkill for simple apps
GraphQL (Apollo or URQL)
Type‑safe, fetch exactly what you need
Requires a GraphQL server, steeper learning curve
Best Practices
Use a dedicated data‑fetching library – TanStack Query handles cache invalidation, request retries, and stale data out of the box.
Centralise API configuration – Create a reusable API client (e.g., Axios instance) that sets base URL, timeout, and auth headers.
Normalise error handling – Wrap each query/mutation in a consistent error boundary or show a toast. Use @tanstack/react-query-devtools during development.
Render lists with FlatList – Render list data returned from API calls with FlatList (or SectionList) to benefit from windowing and memory management. Never use ScrollView for long lists.
Handle offline gracefully – Use @react-native-community/netinfo to detect connectivity. TanStack Query can pause queries when offline and resume when back online.
Abort requests on unmount – TanStack Query cancels in‑flight queries automatically when the component unmounts. For manual fetch, pass an AbortController signal.
Secure tokens – Store auth tokens in expo-secure-store (iOS Keychain / Android EncryptedSharedPreferences) and attach them via Axios interceptors or fetch headers.
Common Pitfalls
Not providing a queryKey – Without it, TanStack Query cannot deduplicate or cache requests correctly.
Storing auth tokens in AsyncStorage – Use expo-secure-store instead; AsyncStorage is not secure.
Using ScrollView for dynamic lists – Always prefer FlatList or SectionList to avoid rendering all items at once.
Ignoring networkMode – Set networkMode: 'offlineFirst' to show cached data immediately when offline.
Forgetting to clean up listeners – Always unsubscribe from NetInfo listeners inside a useEffect cleanup.
53
Explain why accessibility matters in React Native applications and what practices developers should follow.
KnownRevise
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 answerHide 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.
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?
KnownRevise
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.
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.