In React Native, what does the iOS-only Settings module store, and why does Settings.watchKeys not fire after your own Settings.set call?
answer
- wrapper over NSUserDefaults
- synchronous get from a cache
- set merges, null removes
- watchKeys ignores in-app writes
- Android fallback warns, returns null
basics
~20 sSettings wraps iOS NSUserDefaults: get reads synchronously from a JavaScript copy, set writes through to native. watchKeys only reports changes made outside React Native code, such as the Settings app, because the native side ignores notifications caused by set.
solid answer
~50 s`Settings` is React Native's wrapper around iOS `NSUserDefaults`, the per-app key-value store. At startup the native module exports a snapshot of the defaults, so `Settings.get(key)` is synchronous and reads that JavaScript copy. `Settings.set({ key: value })` merges into the copy and writes the values to `NSUserDefaults`; a `null` value removes the key. `Settings.watchKeys(keys, callback)` returns a watch id for `Settings.clearWatch`, and its callback — which receives no arguments — fires only when a watched value changes **outside** React Native code, for example from the app's page in the iOS Settings app or from native code. While `set` writes, the native module ignores its own change notification, so your own writes never trigger the callback. On Android `Settings` is a fallback whose methods warn and `get` returns `null`. `NSUserDefaults` is not encrypted, so secrets belong in secure storage instead.
code
tsx · 21 linesimport { useEffect, useState } from 'react';
import { Settings } from 'react-native';
const KEY = 'hideBlockedImages';
export function useHideBlockedImages(): [boolean, (next: boolean) => void] {
const [value, setValue] = useState<boolean>(() => Settings.get(KEY) === true);
useEffect(() => {
// fires only for changes made outside React Native, e.g. the iOS Settings app
const watchId = Settings.watchKeys(KEY, () => setValue(Settings.get(KEY) === true));
return () => Settings.clearWatch(watchId);
}, []);
const update = (next: boolean) => {
Settings.set({ [KEY]: next });
setValue(next); // no watcher fires for our own write
};
return [value, update];
}go deeper
Recall that Settings is an iOS-only wrapper over NSUserDefaults with get, set, watchKeys and clearWatch.
Explain the startup snapshot behind synchronous get, write-through set with null removal, and why watchKeys ignores in-app writes.
Decide when an iOS Settings-app preference is worth a platform-only path, and keep secrets and cross-platform data out of it.
Frame preference storage per platform: which settings belong in the system Settings app, and how the product keeps both platforms consistent.
## What `Settings` wraps iOS gives every app **`NSUserDefaults`**: a small persistent key-value store for preferences, stored as a property list. Values set there survive app restarts, and an app can also expose some of them on its own page in the iOS **Settings** app. React Native's **`Settings`** module is a thin wrapper around that store, and it exists only on iOS. ## The API | Method | What it does | | --- | --- | | `Settings.get(key)` | Returns the value for `key`, synchronously | | `Settings.set(obj)` | Merges `obj` into the store; a `null` value removes that key | | `Settings.watchKeys(keys, callback)` | Watches one key or an array of keys; returns a watch id | | `Settings.clearWatch(watchId)` | Stops that watcher | ## How it works 1. **Snapshot at startup.** When the module loads, the native side exports the entire `NSUserDefaults` dictionary as a constant. The JavaScript module keeps it as a private cache. 2. **Synchronous reads.** `get` reads the cache, so it needs no `await` — unlike most storage APIs in React Native. 3. **Write-through sets.** `set` merges into the cache immediately and calls the native `setValues`, which writes each key and removes keys whose value is `null`. 4. **External change notifications.** The native module listens for the system's "defaults changed" notification and sends the new dictionary to JavaScript, which updates the cache and calls watchers for keys whose value actually changed. 5. **Self-writes are ignored.** While `setValues` runs, the native module sets an "ignoring updates" flag, so the notification caused by your own `set` is dropped. React Native's documentation states it directly: `watchKeys()` ignores internal `set()` calls and fires only on changes performed outside React Native code. The watcher callback receives **no arguments**; call `Settings.get` inside it to read the new value. ## A forum app example A community forum app exposes a "Hide images from blocked users" switch on its page in the iOS Settings app. The React Native screen reads it with `Settings.get('hideBlockedImages')` on mount and calls `Settings.watchKeys('hideBlockedImages', ...)` so the feed updates if the user flips the switch in the Settings app while the forum is in the background. The in-app toggle writes with `Settings.set` and updates its own state directly, because no watcher will fire for that write. ## What it is not for - **Secrets.** `NSUserDefaults` is not encrypted; tokens and passwords belong in the platform's secure storage. - **Large or structured data.** It is a preferences store; values must be property-list compatible, and the whole dictionary is copied into JavaScript at startup. - **Cross-platform storage.** On Android `Settings` resolves to a fallback: `get` warns and returns `null`, `set` and `clearWatch` warn, and `watchKeys` warns and returns `-1`. Cross-platform preferences need a storage library that works on both. ## Settings versus other storage | Need | Fits | Why | | --- | --- | --- | | A preference also shown in the iOS Settings app | `Settings` | It reads and writes the same `NSUserDefaults` the Settings app uses | | Cross-platform key-value data | A storage library that supports iOS and Android | `Settings` is a warning fallback on Android | | Tokens, passwords, keys | The platform's secure storage | `NSUserDefaults` is not encrypted | | Values native iOS code already reads | `Settings` | Native code and JavaScript share one store | ## Common mistakes - Expecting `watchKeys` to fire after `set` and wiring UI updates to it. - Awaiting `Settings.get`, or treating it as asynchronous, which hides the fact that it reads a startup snapshot updated by notifications. - Using it on Android behind no guard and shipping a feature that silently reads `null`. ## Interview summary Say it wraps `NSUserDefaults`, that `get` is synchronous from a cached snapshot, that `set` writes through and `null` removes, that `watchKeys` only reports external changes, and that Android gets a warning fallback.
- How do you delete a key with React Native's Settings module on iOS?Pass it with a `null` value to `Settings.set`, for example `Settings.set({ draftFilter: null })`. The native module removes any key whose value cannot be converted to a property-list value, and `null` is the simplest such value.
- Why is React Native's Settings.get synchronous when most storage reads in React Native are asynchronous?The native module exports the whole `NSUserDefaults` dictionary as a constant when the module loads, and JavaScript keeps it in a cache. `get` reads that cache; `set` and external-change notifications keep it current.
saying these in an interview costs you the question
- Settings.get returns a promise
- watchKeys fires after every Settings.set
- Settings works the same on Android through SharedPreferences
- NSUserDefaults is a good place for auth tokens
- The watchKeys callback receives the new value