skip to content

In the Expo SDK, what does a permission hook such as useForegroundPermissions return, and what does it do when the component mounts?

level: middleimportance: should knowfreq 35%

answer

  1. a tuple of three
  2. null before the first read
  3. get on mount by default
  4. request: true asks on mount
  5. built by createPermissionHook

basics

~20 s

An Expo permission hook returns [response, requestPermission, getPermission]. The response is null until the first read resolves; by default the hook only reads the status on mount, and passing { request: true } makes it request on mount instead.

solid answer

~40 s

Hooks such as `useForegroundPermissions` from expo-location or `useCameraPermissions` from expo-camera are made with `createPermissionHook`, which wraps a package's get and request functions. The hook returns a tuple: the current `PermissionResponse` or `null`, a `requestPermission` function and a `getPermission` function, and both functions update the hook's state and return the response. Its options default to `{ get: true, request: false }`, so on mount it reads the status without prompting; `{ request: true }` requests on mount instead. The response is `null` on the first render, so the screen must handle that loading state. The hook does not watch for changes made in Settings: call `getPermission` to refresh. Each component using the hook holds its own copy of the state.

code

tsx · 26 lines
tsx
import * as Location from 'expo-location';
import { ActivityIndicator, Button, Text, View } from 'react-native';

export function CafesHeader() {
  // Default options: reads the status on mount, never prompts by itself.
  const [permission, requestPermission] = Location.useForegroundPermissions();

  if (permission === null) return <ActivityIndicator />; // not read yet, not denied

  if (!permission.granted && permission.canAskAgain) {
    return (
      <View>
        <Text>Find cafes near you</Text>
        <Button
          title="Use my location"
          onPress={async () => {
            const res = await requestPermission();
            if (!res.granted) console.log('Location declined; showing city search');
          }}
        />
      </View>
    );
  }

  return <Text>{permission.granted ? 'Cafes near you' : 'Search cafes by city'}</Text>;
}

go deeper

for a junior

Recall the tuple order, response then requestPermission then getPermission, and that the response starts as null.

for a middle

Explain the get and request defaults, what request: true changes, and why null must be handled as loading rather than denial.

for a senior

Point out the stale-state traps: no subscription to Settings changes, separate state per hook instance, and how you re-read on focus.

for a principal

Argue for one permission contract across the app, including custom modules exposed through createPermissionHook, so every screen handles loading, denial and recovery the same way.

## Where the hooks come from Expo packages that guard a capability export a **permission hook** next to their get and request functions: `useForegroundPermissions` and `useBackgroundPermissions` in `expo-location`, `useCameraPermissions` and `useMicrophonePermissions` in `expo-camera`, and so on. None of them is hand-written per package. Each is produced by **`createPermissionHook`**, a factory in `expo-modules-core` that the `expo` package re-exports: ```ts export const useForegroundPermissions = createPermissionHook({ getMethod: getForegroundPermissionsAsync, requestMethod: requestForegroundPermissionsAsync, }); ``` That shared factory is why every Expo permission hook behaves the same way. ## What the hook returns The hook returns a **three-element tuple**: 1. **`response`**: the latest `PermissionResponse` (`status`, `granted`, `canAskAgain`, `expires`), or **`null`** before the first read has resolved. 2. **`requestPermission()`**: calls the package's request function, stores the result in the hook's state and returns it. 3. **`getPermission()`**: calls the package's get function, stores the result and returns it. Because the functions return the response as well as storing it, you can `await requestPermission()` and branch on the result immediately instead of waiting for the next render. ## What happens on mount The hook accepts an options object whose behaviour flags default to **`get: true`** and **`request: false`**: | options | effect on mount | |---|---| | none (defaults) | calls the get function: reads the status, no dialog | | `{ request: true }` | calls the request function: may show the system prompt | | `{ get: false }` | does nothing; `response` stays `null` until you call a function | When `request` is true the hook requests and skips the separate get. Any other keys in the options object are forwarded as options to the package's get and request functions. Most screens should keep the default: read on mount, then call `requestPermission` from a button, so the prompt appears with context. `{ request: true }` suits a screen whose only purpose is the capability, such as a full-screen scanner the user deliberately opened. ## Handling the first render On the first render `response` is `null`, which is **not** the same as denied. A robust screen treats it as "still loading": - render nothing or a placeholder while `response` is `null`; - only then branch on `granted` and `canAskAgain`. Treating `null` as denied flashes a "Location is off" card for a frame on every mount, which is a common review comment. ## What the hook does not do - **It does not subscribe to changes.** If the user toggles the permission in Settings, the stored response stays stale until something calls `getPermission`. - **It does not share state between components.** Each component that calls the hook has its own `useState`; a grant obtained through one instance does not update another until that one re-reads. - **It does not update after unmount.** The factory tracks whether the component is still mounted and drops results that arrive later. ## Hook or plain functions? Both styles use the same underlying calls, so the choice is about where the state lives: - **Use the hook** in a component that renders differently by permission state; it gives you a stored response, a loading state and functions that keep that state current. - **Use the plain get and request functions** in non-component code, such as a service that runs before a location query, or a flow that needs the answer once and does not render from it. - **Mixing is fine**, with one caveat: a plain request made elsewhere does not update the hook's stored response, so call the hook's `getPermission` afterwards. ## Writing your own hook If you expose a guarded capability from your own native module, you can reuse the same contract: export a get and a request function that resolve to a `PermissionResponse`, then pass them to `createPermissionHook` from `expo`. Your consumers get the identical tuple, defaults and loading behaviour as every SDK package, which keeps permission handling uniform across the app.

  • Two screens both call useCameraPermissions and the user grants access on one of them; what does the other show?
    Its own stale response. Each hook instance keeps its own state, so the second screen still holds whatever it read on mount until it calls its `getPermission` function or remounts. Re-reading on focus keeps both in step.
  • When is { request: true } a reasonable option for an Expo permission hook?
    When the screen exists only for that capability and the user just chose to open it, for example a full-screen scanner reached from a Scan button. The prompt then still arrives with context. On a general screen it prompts on every mount while the answer is open, which feels like nagging.

saying these in an interview costs you the question

  • A null response on the first render means the permission is denied.
  • The hook shows the system prompt as soon as the component mounts by default.
  • The hook updates automatically when the user changes the permission in Settings.
  • All components using the same permission hook share one state.
  • Each Expo package hand-writes its own permission hook logic.