skip to content

In React Native, how do you show a native iOS action sheet with ActionSheetIOS.showActionSheetWithOptions, and what happens if the same call runs on Android?

level: juniorimportance: should knowfreq 35%

answer

  1. UIAlertController in action-sheet style
  2. options array of button titles
  3. cancel and destructive indices
  4. callback gets the tapped index
  5. Android: invariant error, no sheet

basics

~20 s

ActionSheetIOS.showActionSheetWithOptions takes an options array of button titles plus indices for cancel and destructive buttons, and calls back with the zero-based index tapped. It is iOS-only: on Android the native module is missing and the call throws.

solid answer

~40 s

`ActionSheetIOS.showActionSheetWithOptions(options, callback)` presents iOS's native action sheet. `options.options` is the required array of button titles; `cancelButtonIndex` marks the cancel button, `destructiveButtonIndex` (a number or an array) styles destructive ones in red, `disabledButtonIndices` disables some, and `title`, `message`, `tintColor` and `userInterfaceStyle` adjust the look. The callback receives the zero-based index of the tapped button, so the app maps indices back to actions — keep the titles and the handlers in one array to avoid off-by-one bugs. On iPad the sheet is a popover; `anchor` takes a native node handle to point it at a view. On Android the underlying `ActionSheetManager` module does not exist, so the call fails an invariant and throws; Android needs a different UI, such as a bottom sheet or `Alert.alert`, whose Android dialog shows at most three buttons.

code

tsx · 21 lines
tsx
import { ActionSheetIOS } from 'react-native';

type Entry = { title: string; run?: () => void; destructive?: boolean; cancel?: boolean };

export function showPostActions(onShare: () => void, onCopyLink: () => void, onReport: () => void) {
  const entries: Entry[] = [
    { title: 'Share post', run: onShare },
    { title: 'Copy link', run: onCopyLink },
    { title: 'Report post', run: onReport, destructive: true },
    { title: 'Cancel', cancel: true },
  ];

  ActionSheetIOS.showActionSheetWithOptions(
    {
      options: entries.map(e => e.title),
      destructiveButtonIndex: entries.findIndex(e => e.destructive),
      cancelButtonIndex: entries.findIndex(e => e.cancel),
    },
    buttonIndex => entries[buttonIndex]?.run?.(),
  );
}

go deeper

for a junior

Recall the call shape: an options array of titles, cancel and destructive indices, and a callback that receives the tapped index.

for a middle

Explain index-based dispatch and how to keep it safe, the iPad popover and anchor, and the Android failure mode.

for a senior

Design one cross-platform action menu API that uses the native iOS sheet and an Android component without duplicating action logic.

for a principal

Decide when native platform pickers are worth their divergence versus one shared menu component across the product.

## What an action sheet is An **action sheet** is the iOS pattern for "choose one of these actions about the thing you just touched": a stack of buttons that slides up from the bottom, with **Cancel** separated at the end and dangerous actions in red. React Native exposes it through **`ActionSheetIOS`**, a thin JavaScript wrapper over a native iOS module that builds a `UIAlertController` in action-sheet style. In a community forum app, long-pressing a post is the classic trigger: *Share post*, *Copy link*, *Report post*, *Cancel*. ## The call ```tsx ActionSheetIOS.showActionSheetWithOptions( { options: ['Share post', 'Copy link', 'Report post', 'Cancel'], destructiveButtonIndex: 2, cancelButtonIndex: 3, title: 'Post by @river', }, buttonIndex => { // 0-based index into options }, ); ``` | Option | Type | Effect | | --- | --- | --- | | `options` | string array, **required** | Button titles, in order | | `cancelButtonIndex` | number | Styles that button as Cancel | | `destructiveButtonIndex` | number or number array | Styles one or several buttons as destructive | | `disabledButtonIndices` | number array | Greys out and disables those buttons | | `title`, `message` | string | Text above the buttons | | `tintColor`, `cancelButtonTintColor`, `disabledButtonTintColor` | color | Button title colours | | `userInterfaceStyle` | `'light'` or `'dark'` | Forces an appearance instead of following the system | | `anchor` | number | Native node handle the iPad popover points at | The **callback** receives one number: the zero-based index of the tapped button. There are no per-button `onPress` handlers, which is the main source of bugs — reorder the titles and every index-based `switch` silently maps to the wrong action. ## Keeping indices safe 1. Build one array of `{ title, run }` entries. 2. Derive `options` from the titles and compute `cancelButtonIndex` and `destructiveButtonIndex` with `findIndex`, never as literals. 3. In the callback, call `entries[buttonIndex].run()`. The native module also guards the callback so it runs **at most once** per sheet; its source notes that tapping outside the sheet on iPad can trigger a button handler more than once, and only the first reaches JavaScript. ## iPad On iPad an action sheet is shown as a **popover**. React Native always gives it a source view, so it never needs an anchor to appear: without `anchor` the popover is presented from the root view with no arrow; with `anchor` set to a node handle (for example `findNodeHandle(ref.current)`) it points at that view. ## Dismissing it from code `ActionSheetIOS.dismissActionSheet()` closes the topmost action sheet that React Native presented, and logs a warning if none is showing. In a forum app that is useful when the post disappears while the sheet is open — a moderator removed it, or a live update replaced the thread — so the user cannot pick *Report* on a post that no longer exists. The callback of a sheet dismissed this way should not be relied on for cleanup; keep the state change in the code that calls `dismissActionSheet()`. ## Action sheet or alert on iOS? - **Action sheet**: several actions about the thing the user just touched, slid up from the bottom. - **`Alert.alert`**: a centred dialog for a message or a short confirmation, such as *Report this post?* with *Cancel* and *Report*. A typical report flow uses both: the sheet to choose *Report post*, then an alert or prompt to confirm or collect a reason. ## The share sheet is a sibling method `ActionSheetIOS.showShareActionSheetWithOptions(options, failureCallback, successCallback)` opens the iOS **share sheet** for a `url` and/or `message`, with `subject` and `excludedActivityTypes`. For cross-platform sharing React Native also has the core `Share` API, which is usually what a forum app's *Share post* button calls. ## What happens on Android `ActionSheetIOS` is exported on every platform, but its native module is registered only on iOS. On Android the module lookup returns nothing, and `showActionSheetWithOptions` fails its first `invariant` checks with **"ActionSheetManager doesn't exist"** — an exception, not a silent no-op. Options for Android: - **`Alert.alert`** with buttons — but the Android dialog uses **at most three** buttons (neutral, negative, positive) and ignores the rest, so a four-action menu does not fit. - **A bottom sheet or menu component** rendered by the app, usually shared by both platforms when design wants identical behaviour. - **A platform branch** that uses the native sheet on iOS and the component on Android. ## Interview summary Name the required `options` array, the cancel and destructive indices, the index-based callback and the Android failure mode. Mentioning index drift, the iPad popover and the three-button limit of Android alerts shows you have shipped one.

  • In React Native's ActionSheetIOS, how do you mark two buttons as destructive?
    Pass an array to `destructiveButtonIndex`, for example `destructiveButtonIndex: [2, 3]`. The JavaScript wrapper accepts a number or an array and normalises both into a list of destructive indices before calling the native module.
  • Why can't a four-action React Native menu reuse Alert.alert on Android?
    React Native's Android alert maps buttons onto the dialog's neutral, negative and positive slots and keeps at most three, ignoring the rest. A menu with four actions plus cancel needs a bottom sheet or menu component instead.

saying these in an interview costs you the question

  • ActionSheetIOS silently does nothing on Android
  • Each option has its own onPress handler
  • destructiveButtonIndex only accepts a single number
  • An iPad action sheet crashes without an anchor
  • Alert.alert can show any number of buttons on Android