Saving and Retrieving User Preferences in React Native
Course: React Native Training
Topic: Saving and Retrieving User Preferences
Course Link: React Native Training at JustAcademy
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction to User Preferences
User preferences are settings and choices that users make while interacting with a mobile application. A React Native application can save these choices so they remain available when the user closes and reopens the app.
For example, a user may choose dark mode, select a preferred language, enable notifications, adjust text size, or choose a default screen. Saving these preferences makes the application more personalized and convenient.
Without persistent storage, preference values held only in component state may return to their default values after the application restarts. Persistent storage allows an application to save selected values and retrieve them later.
Examples of User Preferences
- Theme: Light mode, dark mode, or system default.
- Language: English, Hindi, or another supported language.
- Notifications: Whether optional notifications are enabled.
- Text size: Standard, medium, or large text.
- Dashboard settings: The user's preferred home screen or dashboard layout.
- Privacy settings: Optional personalization and analytics choices.
- Display options: Compact layout, list view, or grid view.
2. Learning Objectives
After completing this topic, learners should be able to:
- Understand the difference between temporary state and persistent data.
- Choose a suitable storage method for user preferences.
- Install and use AsyncStorage in a React Native project.
- Save, retrieve, update, and remove preference values.
- Convert JavaScript objects to JSON strings and back.
- Load saved preferences when a component starts.
- Manage asynchronous storage operations and errors.
- Build a settings screen that remembers user choices.
- Apply appropriate security and data-management practices.
3. Understanding Temporary State and Persistent Storage
React Native applications commonly use React state to manage values that affect the current interface. State is useful for immediate updates, but component state alone does not provide long-term persistence across application restarts.
| Feature |
React State |
Persistent Storage |
| Purpose |
Manages current UI values |
Stores values for future sessions |
| Survives component unmounting |
Not guaranteed |
Usually, if saved successfully |
| Survives app restart |
No, not by itself |
Yes, depending on the storage system |
| Updates the UI automatically |
Yes, when state changes |
No, storage changes must be reflected in state |
| Typical use |
Loading indicators, form input, open menus |
Theme, language, and other saved settings |
A common pattern is to use both systems together: React state updates the screen immediately, while persistent storage keeps the preference available for the next session.
4. Choosing a Storage Method
The best storage method depends on the type of preference, its sensitivity, the amount of data, and whether the application needs structured queries.
| Storage Method |
Suitable Use |
Important Consideration |
| AsyncStorage |
Small, non-sensitive key-value settings |
Stores strings and is not encrypted by default |
| Expo SecureStore |
Small sensitive values such as tokens |
Uses platform-backed secure storage; understand platform limits |
| SQLite |
Structured local data and relational records |
Requires database design and queries |
| Remote backend |
Preferences synchronized across devices |
Requires network access, authentication, and conflict handling |
| React state or Context |
Current-session UI preferences |
Does not persist values on its own |
For simple settings such as theme, language, and a compact-view toggle, AsyncStorage is often sufficient. Do not store passwords, secret keys, or sensitive authentication data in ordinary AsyncStorage.
5. Introduction to AsyncStorage
AsyncStorage is an asynchronous, persistent, unencrypted key-value storage solution commonly used in React Native applications. It lets an application save strings under unique keys and retrieve them later.
Because its API is asynchronous, operations such as reading and writing values return Promises. Use async and await, or handle the returned Promise with .then() and .catch().
Common AsyncStorage Operations
| Method |
Purpose |
setItem(key, value) |
Saves a string value under a key. |
getItem(key) |
Retrieves a saved string or returns null if the key does not exist. |
removeItem(key) |
Removes a particular key. |
multiGet(keys) |
Retrieves multiple keys. |
multiSet(entries) |
Saves multiple key-value pairs. |
clear() |
Removes all AsyncStorage data for the application storage namespace; use with care. |
Always check the current installation instructions and API for the AsyncStorage version used in your project, because package versions and import patterns can differ.
6. Installing AsyncStorage
For a React Native project using the community AsyncStorage package, install the dependency using the package manager appropriate for your project.
Using npm
npm install @react-native-async-storage/async-storage
Using Yarn
yarn add @react-native-async-storage/async-storage
If you are developing with Expo, follow the package installation guidance for your Expo SDK, commonly using:
npx expo install @react-native-async-storage/async-storage
For native projects, complete any platform-specific setup required by your React Native version. If you change native dependencies, rebuild the native application when required. Refer to the official documentation before proceeding.
Official documentation: AsyncStorage Documentation.
7. Importing AsyncStorage
Import AsyncStorage at the top of the component or utility module where you need to read and write preferences.
import AsyncStorage from '@react-native-async-storage/async-storage';
In this example, the package is used to store a theme preference under a descriptive key.
8. Saving a Simple Preference
Use setItem() to save a string value. The first argument is the key, and the second argument is the value.
import AsyncStorage from '@react-native-async-storage/async-storage';
const saveTheme = async (theme) => {
try {
await AsyncStorage.setItem('user_theme', theme);
console.log('Theme preference saved');
} catch (error) {
console.error('Unable to save theme:', error);
}
};
// Example usage
saveTheme('dark');
In this example, the value dark is saved under user_theme. The application can retrieve this key when it starts again.
Why Use a Try-Catch Block?
- Storage operations can fail.
- Error handling prevents an unhandled rejection.
- The application can show an appropriate message or continue with a default setting.
- Developers can log diagnostic information during development without exposing sensitive data.
9. Retrieving a Saved Preference
Use getItem() to retrieve a saved value. If the key has never been saved or has been removed, the result is normally null.
import AsyncStorage from '@react-native-async-storage/async-storage';
const getSavedTheme = async () => {
try {
const theme = await AsyncStorage.getItem('user_theme');
if (theme !== null) {
console.log('Saved theme:', theme);
return theme;
}
return 'light'; // Default preference
} catch (error) {
console.error('Unable to retrieve theme:', error);
return 'light';
}
};
The default value is important because the user may be opening the app for the first time. Always design the interface to work when no saved preference exists.
10. Updating and Removing Preferences
Saving a new value using an existing key updates the stored value. To remove one preference, use removeItem().
Update a Preference
const updateLanguage = async (language) => {
try {
await AsyncStorage.setItem('user_language', language);
} catch (error) {
console.error('Unable to update language:', error);
}
};
updateLanguage('hi');
Remove a Preference
const removeLanguagePreference = async () => {
try {
await AsyncStorage.removeItem('user_language');
} catch (error) {
console.error('Unable to remove language:', error);
}
};
When a preference is removed, the application should decide whether to use a default value, ask the user to choose again, or restore a system setting.
11. Saving Multiple Preferences
An application often needs to save several settings, such as theme, language, notification choice, and text size. You can store each preference separately or store a small settings object as a JSON string.
Option A: Save Preferences Separately
const savePreferences = async () => {
try {
await AsyncStorage.setItem('user_theme', 'dark');
await AsyncStorage.setItem('user_language', 'en');
await AsyncStorage.setItem('notifications_enabled', 'true');
} catch (error) {
console.error('Unable to save preferences:', error);
}
};
Option B: Save a Settings Object
const preferences = {
theme: 'dark',
language: 'en',
notificationsEnabled: true,
textSize: 'medium'
};
const saveAllPreferences = async () => {
try {
await AsyncStorage.setItem(
'user_preferences',
JSON.stringify(preferences)
);
} catch (error) {
console.error('Unable to save preferences:', error);
}
};
The object approach keeps related settings together and can make retrieval convenient. Separate keys may be more practical when settings are independent or frequently updated. Choose a consistent approach and plan how to handle future changes to the stored data structure.
12. Understanding JSON.stringify() and JSON.parse()
AsyncStorage stores strings. JavaScript objects, arrays, and Boolean values must therefore be converted before they can be stored as structured data.
JSON.stringify() converts a JavaScript value into a JSON string.
JSON.parse() converts a valid JSON string back into a JavaScript value.
Example
const settings = {
theme: 'dark',
language: 'en',
notificationsEnabled: false
};
const serializedSettings = JSON.stringify(settings);
console.log(serializedSettings);
const restoredSettings = JSON.parse(serializedSettings);
console.log(restoredSettings.theme); // dark
Always handle parsing errors. Stored data may be malformed, outdated, or written by an earlier version of the application.
13. Retrieving a Settings Object Safely
The following helper retrieves an object from AsyncStorage and returns default settings if no valid saved value is available.
import AsyncStorage from '@react-native-async-storage/async-storage';
const DEFAULT_PREFERENCES = {
theme: 'light',
language: 'en',
notificationsEnabled: true,
textSize: 'medium'
};
const loadPreferences = async () => {
try {
const savedValue = await AsyncStorage.getItem('user_preferences');
if (savedValue === null) {
return DEFAULT_PREFERENCES;
}
const parsedValue = JSON.parse(savedValue);
if (
parsedValue === null ||
typeof parsedValue !== 'object' ||
Array.isArray(parsedValue)
) {
return DEFAULT_PREFERENCES;
}
return {
...DEFAULT_PREFERENCES,
...parsedValue
};
} catch (error) {
console.error('Unable to load preferences:', error);
return DEFAULT_PREFERENCES;
}
};
This helper combines saved values with defaults so newly introduced settings can receive a default even when an older saved object does not contain them. In a production application, validate each field and its allowed values instead of trusting arbitrary stored content.
14. Managing Preferences with React State
Persistent storage does not automatically update the React interface. Use React state to hold the active preference and update it when the user changes a setting.
For example, when a user selects dark mode, the application can update state immediately and save the choice to AsyncStorage. The interface responds to the state change, while storage preserves the value for future sessions.
import React, { useState } from 'react';
const [theme, setTheme] = useState('light');
const changeTheme = async (nextTheme) => {
setTheme(nextTheme);
try {
await AsyncStorage.setItem('user_theme', nextTheme);
} catch (error) {
console.error('Unable to save theme:', error);
}
};
This snippet illustrates the pattern inside a React component. Make sure AsyncStorage is imported and the state declaration is placed inside the component function. Consider how to handle a failed save so the interface does not misleadingly imply that the choice was persisted.
15. Loading Preferences with useEffect()
The React useEffect() Hook is commonly used to load preferences when a component first appears. Since storage access is asynchronous, the component may briefly show a loading state or a default theme before the saved setting is available.
import React, { useEffect, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
function SettingsScreen() {
const [theme, setTheme] = useState('light');
const [loading, setLoading] = useState(true);
useEffect(() => {
let active = true;
const loadTheme = async () => {
try {
const savedTheme = await AsyncStorage.getItem('user_theme');
if (active && savedTheme !== null) {
setTheme(savedTheme);
}
} catch (error) {
console.error('Unable to load theme:', error);
} finally {
if (active) {
setLoading(false);
}
}
};
loadTheme();
return () => {
active = false;
};
}, []);
if (loading) {
return null; // Replace with a loading indicator if appropriate
}
return null; // Render the settings interface here
}
export default SettingsScreen;
The example demonstrates the loading lifecycle, but the null return values are placeholders. Replace them with your application's loading indicator and actual settings UI. The cleanup flag prevents a state update after the effect has been cleaned up.
16. Building a Theme Preference
A theme preference lets users choose between light and dark appearances. Save a stable value such as light, dark, or system instead of saving an entire collection of visual styles.
Theme Update Function
const saveThemePreference = async (nextTheme) => {
if (!['light', 'dark', 'system'].includes(nextTheme)) {
return;
}
setTheme(nextTheme);
try {
await AsyncStorage.setItem('user_theme', nextTheme);
} catch (error) {
console.error('Unable to save theme:', error);
}
};
Your application can use the selected theme to determine background colors, text colors, borders, and other visual styles. For a system theme, use the appropriate platform appearance API and decide how changes in the operating system should affect the active theme.
17. Saving Language Preferences
Language settings allow users to choose the language in which application content is displayed. Store a language code rather than duplicating all translated text in local storage.
const saveLanguagePreference = async (languageCode) => {
const supportedLanguages = ['en', 'hi', 'fr'];
if (!supportedLanguages.includes(languageCode)) {
return;
}
try {
await AsyncStorage.setItem('user_language', languageCode);
setLanguage(languageCode);
} catch (error) {
console.error('Unable to save language:', error);
}
};
Here, setLanguage represents a state setter or application-level language update function that must be defined in your project. After loading the saved code, configure your localization library to display the corresponding translations.
18. Saving Boolean Preferences
JavaScript Boolean values are not stored directly as Booleans in AsyncStorage. Convert them to strings when saving and convert them back when retrieving.
const saveNotificationsPreference = async (enabled) => {
try {
await AsyncStorage.setItem(
'notifications_enabled',
JSON.stringify(enabled)
);
} catch (error) {
console.error('Unable to save notification preference:', error);
}
};
const getNotificationsPreference = async () => {
try {
const value = await AsyncStorage.getItem('notifications_enabled');
if (value === null) {
return true;
}
return JSON.parse(value) === true;
} catch (error) {
console.error('Unable to load notification preference:', error);
return true;
}
};
Choose a default that matches your application's product requirements and privacy commitments. A saved notification preference controls the application's choice, but it does not replace operating-system permission checks or the user's device-level notification settings.
19. Creating a Reusable Preference Storage Utility
As an application grows, saving and loading preferences directly inside every screen can create duplicated code. A utility module centralizes the storage logic and makes it easier to test, update, and maintain.
preferencesStorage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
const PREFERENCES_KEY = 'user_preferences';
export const DEFAULT_PREFERENCES = {
theme: 'light',
language: 'en',
notificationsEnabled: true,
textSize: 'medium'
};
export const savePreferences = async (preferences) => {
try {
const jsonValue = JSON.stringify(preferences);
await AsyncStorage.setItem(PREFERENCES_KEY, jsonValue);
return true;
} catch (error) {
console.error('Failed to save preferences:', error);
return false;
}
};
export const getPreferences = async () => {
try {
const jsonValue = await AsyncStorage.getItem(PREFERENCES_KEY);
if (jsonValue === null) {
return { ...DEFAULT_PREFERENCES };
}
const parsed = JSON.parse(jsonValue);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return { ...DEFAULT_PREFERENCES };
}
return {
...DEFAULT_PREFERENCES,
...parsed
};
} catch (error) {
console.error('Failed to retrieve preferences:', error);
return { ...DEFAULT_PREFERENCES };
}
};
export const resetPreferences = async () => {
try {
await AsyncStorage.removeItem(PREFERENCES_KEY);
return true;
} catch (error) {
console.error('Failed to reset preferences:', error);
return false;
}
};
This utility uses the traditional default AsyncStorage export for compatibility with existing projects. Current AsyncStorage releases also provide an instance-based API, so check the installed version's documentation and use the API appropriate for your project. The returned Boolean indicates whether the operation succeeded; the calling component should respond appropriately when it returns false.
20. Complete Example: User Preferences Screen
The following example demonstrates loading saved preferences, changing the theme, choosing a language, toggling a notification preference, and resetting the settings. It uses React Native's built-in components and the utility functions from the previous section.
import React, { useEffect, useState } from 'react';
import {
View,
Text,
Switch,
Pressable,
ActivityIndicator,
StyleSheet
} from 'react-native';
import {
DEFAULT_PREFERENCES,
getPreferences,
savePreferences,
resetPreferences
} from './preferencesStorage';
export default function PreferencesScreen() {
const [preferences, setPreferences] = useState(DEFAULT_PREFERENCES);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState('');
useEffect(() => {
let active = true;
const load = async () => {
const saved = await getPreferences();
if (active) {
setPreferences(saved);
setLoading(false);
}
};
load();
return () => {
active = false;
};
}, []);
const updatePreference = async (key, value) => {
const nextPreferences = {
...preferences,
[key]: value
};
setPreferences(nextPreferences);
setSaving(true);
setMessage('');
const success = await savePreferences(nextPreferences);
setSaving(false);
setMessage(
success
? 'Preferences saved.'
: 'Could not save preferences. Please try again.'
);
};
const reset = async () => {
setSaving(true);
setMessage('');
const success = await resetPreferences();
if (success) {
setPreferences({ ...DEFAULT_PREFERENCES });
setMessage('Preferences reset to defaults.');
} else {
setMessage('Could not reset preferences.');
}
setSaving(false);
};
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" />
<Text>Loading preferences...</Text>
</View>
);
}
const dark = preferences.theme === 'dark';
return (
<View
style={[
styles.container,
{ backgroundColor: dark ? '#202124' : '#FFFFFF' }
]}
>
<Text
style={[
styles.heading,
{ color: dark ? '#FFFFFF' : '#202124' }
]}
>
User Preferences
</Text>
<Text style={{ color: dark ? '#FFFFFF' : '#202124' }}>
Theme: {preferences.theme}
</Text>
<View style={styles.row}>
<Text style={{ color: dark ? '#FFFFFF' : '#202124' }}>
Dark mode
</Text>
<Switch
value={dark}
onValueChange={(enabled) =>
updatePreference('theme', enabled ? 'dark' : 'light')
}
/>
</View>
<Text style={{ color: dark ? '#FFFFFF' : '#202124' }}>
Language: {preferences.language}
</Text>
<View style={styles.row}>
<Pressable
style={styles.button}
onPress={() => updatePreference('language', 'en')}
disabled={saving}
>
<Text style={styles.buttonText}>English</Text>
</Pressable>
<Pressable
style={styles.button}
onPress={() => updatePreference('language', 'hi')}
disabled={saving}
>
<Text style={styles.buttonText}>Hindi</Text>
</Pressable>
</View>
<View style={styles.row}>
<Text style={{ color: dark ? '#FFFFFF' : '#202124' }}>
Optional notifications
</Text>
<Switch
value={preferences.notificationsEnabled}
onValueChange={(enabled) =>
updatePreference('notificationsEnabled', enabled)
}
/>
</View>
<Pressable
style={styles.button}
onPress={reset}
disabled={saving}
>
<Text style={styles.buttonText}>Reset Preferences</Text>
</Pressable>
{saving && <ActivityIndicator />}
{message !== '' && (
<Text
accessibilityRole="alert"
style={{ color: dark ? '#FFFFFF' : '#202124' }}
>
{message}
</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
gap: 16
},
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 12
},
heading: {
fontSize: 24,
fontWeight: 'bold'
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 12
},
button: {
backgroundColor: '#356AE6',
padding: 12,
borderRadius: 8
},
buttonText: {
color: '#FFFFFF',
fontWeight: '600'
}
});
How the Example Works
- Initial loading: The screen calls
getPreferences() inside useEffect().
- Default values: If no saved settings exist, the utility returns default preferences.
- State update: Changing a switch or language button creates a new preference object.
- Persistence: The updated object is serialized and saved using
savePreferences().
- Feedback: The screen shows a message when a save or reset operation succeeds or fails.
- Reset: The reset button removes the saved settings key and restores defaults in the interface.
Production consideration: The example updates the interface before the storage operation finishes. If saving fails, the displayed setting can differ from the persisted value. For a production app, consider rolling back the state on failure, retrying the save, or clearly indicating that the setting was not saved. If users can make rapid changes, serialize writes or prevent overlapping saves to avoid older writes overwriting newer preferences.
21. Handling Loading and Saving States
Because storage operations are asynchronous, the application should communicate when preferences are loading or saving. This avoids confusion and helps prevent users from repeatedly pressing controls.
- Show a loading indicator while retrieving preferences.
- Disable controls during operations when appropriate.
- Display a clear success or failure message.
- Provide sensible defaults if saved data is unavailable.
- Prevent outdated asynchronous operations from updating an unmounted component.
- Avoid displaying the settings screen with incorrect values for longer than necessary.
For a polished experience, the application can load essential preferences before rendering the main navigation or show a temporary splash screen until the initial settings are ready.
22. Validating and Versioning Stored Preferences
Saved values should not be assumed to be valid. A user may have data saved by an older application version, malformed JSON, or unexpected values. Validate preferences before applying them.
Example of Field Validation
const validatePreferences = (value) => {
const defaults = {
theme: 'light',
language: 'en',
notificationsEnabled: true,
textSize: 'medium'
};
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return defaults;
}
return {
theme: ['light', 'dark', 'system'].includes(value.theme)
? value.theme
: defaults.theme,
language: ['en', 'hi', 'fr'].includes(value.language)
? value.language
: defaults.language,
notificationsEnabled:
typeof value.notificationsEnabled === 'boolean'
? value.notificationsEnabled
: defaults.notificationsEnabled,
textSize: ['small', 'medium', 'large'].includes(value.textSize)
? value.textSize
: defaults.textSize
};
};
Use field validation after parsing stored JSON and before using it in the interface. For more complex settings, store a schema version with the preferences and migrate old data when the application introduces a new format.
Versioned Preferences Example
const preferences = {
version: 1,
theme: 'dark',
language: 'en'
};
await AsyncStorage.setItem(
'user_preferences',
JSON.stringify(preferences)
);
When the structure changes, inspect the saved version and migrate older data into the new schema. Do not blindly overwrite existing preferences during an application update.
23. Managing Preferences Across Screens
When several screens use the same settings, repeatedly loading preferences in each screen can cause duplicated logic and inconsistent updates. A shared state solution can provide one source of truth.
- React Context: Useful for sharing theme or language settings across a moderate-sized application.
- Redux: Suitable when preferences are part of a larger centralized application state.
- Zustand: A lightweight store that can be combined with persistence middleware.
- Dedicated settings service: Useful for keeping storage logic separate from the UI.
Persistent storage and shared application state solve different problems. Storage preserves values between sessions, while Context or a state-management library makes the current values accessible to components. An application may use both.
24. Syncing Preferences with a Remote Server
If users sign in on multiple devices, local preferences alone cannot keep all devices synchronized. A backend API can store preferences against the authenticated user account.
A common synchronization flow is:
- Load local preferences to provide a fast initial experience.
- Authenticate the user and request server-side preferences.
- Validate the remote data and compare it with local data.
- Apply a defined conflict-resolution rule.
- Update the application state and persist the chosen values locally.
- Send changes to the server when the user updates a preference.
- Handle network failures and retry when appropriate.
Define whether the server, the latest user action, or a timestamp determines the winning value when local and remote settings conflict. Avoid allowing an older server response to overwrite a more recent local choice.
25. Security and Privacy Best Practices
- Store only the information needed to provide the chosen experience.
- Do not use ordinary AsyncStorage for passwords, private keys, or sensitive authentication tokens.
- Use an appropriate secure-storage solution for sensitive values.
- Do not treat a local preference as proof of authorization. The backend must enforce access controls.
- Validate values read from storage before using them.
- Do not log private user information or sensitive values in production.
- Explain optional tracking and notification settings clearly and respect the user's choice.
- Keep authentication, authorization, and consent logic separate from visual preferences.
- Define what happens to local preferences when the user logs out or switches accounts.
AsyncStorage is persistent but unencrypted by default. For sensitive values, review the React Native security guidance and the documentation for the selected secure-storage library.
26. Common Errors and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Preference disappears after restarting |
Only React state was updated |
Save the value to persistent storage and load it during initialization. |
| Object is returned as a string |
JSON was not parsed |
Use JSON.parse() after retrieving the saved string. |
| JSON parsing fails |
Stored data is invalid or has a different format |
Catch the error, validate the value, and use safe defaults or migrate it. |
| Settings briefly show the wrong value |
UI renders before storage finishes loading |
Track a loading state or load preferences before showing the main screen. |
| Preference does not update other screens |
Only one component's local state was updated |
Use Context or shared state and synchronize it with persistence. |
| Storage import or build fails |
Dependency installation or native setup is incomplete |
Check the installed package version and official setup instructions. |
| Old value reappears after a rapid change |
Overlapping asynchronous writes completed out of order |
Serialize writes, debounce suitable updates, or use a single coordinated storage service. |
| Saved values behave incorrectly after an update |
Data schema changed |
Validate saved fields and migrate data between schema versions. |
| Notification switch does not enable device notifications |
Application preference was confused with operating-system permission |
Check and request platform permissions separately where required. |
27. Performance and Maintainability Best Practices
- Use descriptive and consistent keys, such as
user_preferences or user_theme.
- Keep the stored object small and avoid saving large API responses as preferences.
- Do not read the same key repeatedly when the value is already available in shared state and has not changed.
- Group related settings when it simplifies retrieval and updates.
- Use separate keys when independent updates or migrations make that approach clearer.
- Keep storage functions in a service or utility module instead of duplicating them across screens.
- Validate values and handle missing keys explicitly.
- Use loading and error states to make asynchronous operations predictable.
- Test persistence after app restarts, upgrades, logout, and account switching.
- Use a dedicated persistence library or state-management integration when the application becomes more complex.
28. Practical Project: Personalized Settings Application
Project objective: Create a React Native settings application that remembers user preferences after the app is closed and reopened.
Functional Requirements
- Allow users to choose light or dark theme.
- Allow users to choose a preferred language.
- Provide a switch for optional in-app notifications.
- Provide a text-size preference.
- Load existing settings when the application opens.
- Save each change to local persistent storage.
- Show loading and error feedback.
- Provide a reset-to-default option.
- Validate stored values and fall back to defaults if necessary.
Suggested Project Structure
MyPreferencesApp/
├── App.js
├── src/
│ ├── screens/
│ │ └── PreferencesScreen.js
│ ├── storage/
│ │ └── preferencesStorage.js
│ ├── context/
│ │ └── PreferencesContext.js
│ └── utils/
│ └── validatePreferences.js
Development Steps
- Create a React Native project and install AsyncStorage.
- Define default preference values.
- Implement storage utility functions.
- Create the preferences screen and its controls.
- Load settings during application initialization.
- Update state and save changes when controls are used.
- Add input validation and error handling.
- Test persistence by restarting the app.
- Test reset behavior and malformed stored data.
- Document the implementation and prepare screenshots for your portfolio.
Expected Result
The application should restore the user's saved settings on subsequent launches, display sensible defaults for a first-time user, and provide clear feedback when a storage operation fails.
29. Exercises and Practice Questions
- Create a function that saves a user's preferred theme.
- Write a function that retrieves the theme and returns
light when no value exists.
- Save an object containing language, text size, and notification preferences.
- Explain why
JSON.stringify() and JSON.parse() are needed for object storage.
- Create a switch that saves a Boolean preference.
- Load settings using
useEffect() and display a loading indicator.
- Write a function that removes a selected preference.
- Implement a reset button that restores default settings.
- Validate a stored theme against
light, dark, and system.
- Describe how you would synchronize preferences across two devices.
- Explain why passwords should not be stored in ordinary AsyncStorage.
- Test the application with malformed JSON and simulate a storage failure.
30. Interview Questions and Answers
Q1. What are user preferences in React Native?
Answer: User preferences are saved application settings such as theme, language, text size, and display options that personalize the user experience.
Q2. Why do we need persistent storage?
Answer: Persistent storage keeps selected values available across component unmounting and application restarts, unlike component state alone.
Q3. What is AsyncStorage?
Answer: AsyncStorage is an asynchronous, persistent key-value storage solution for React Native. It stores string values and is unencrypted by default.
Q4. What does getItem() return when a key does not exist?
Answer: It normally resolves to null. The application should check for this and use a suitable default value.
Q5. How do you store a JavaScript object?
Answer: Convert the object to a string using JSON.stringify(), save it, and use JSON.parse() to reconstruct the object after retrieval.
Q6. What is the role of useEffect() when loading preferences?
Answer: It can initiate asynchronous storage retrieval when a component mounts, allowing the component to populate state with saved values.
Q7. How do you handle storage errors?
Answer: Use try-catch around asynchronous operations, provide safe defaults where appropriate, and show meaningful feedback if saving or loading fails.
Q8. What is the difference between local and remote preferences?
Answer: Local preferences are stored on the device, while remote preferences are stored on a server and can be synchronized across devices for an authenticated user.
Q9. Is AsyncStorage suitable for storing passwords?
Answer: No. It is unencrypted by default. Use an appropriate secure-storage solution for sensitive values and follow the platform's security recommendations.
Q10. How can multiple screens share the same preferences?
Answer: Use a shared state solution such as React Context, Redux, or Zustand and coordinate it with persistent storage.
Q11. How do you reset preferences?
Answer: Remove the relevant storage key or write the default settings, then update the active application state to match.
Q12. What happens if the preferences format changes in a new app version?
Answer: Validate the saved values, use defaults for missing or invalid fields, and migrate older data using a schema version when necessary.
31. Quick Revision Checklist
- Understand temporary state versus persistent storage.
- Choose an appropriate storage method for the data.
- Install and configure AsyncStorage for the project version.
- Use
setItem() to save string values.
- Use
getItem() to retrieve values.
- Use
removeItem() to delete a selected preference.
- Use
JSON.stringify() and JSON.parse() for structured data.
- Load saved settings during application initialization.
- Keep React state synchronized with storage.
- Handle loading, saving, parsing, and storage errors.
- Validate data and manage schema changes.
- Keep sensitive information out of unencrypted storage.
- Test app restarts, reset behavior, and failure scenarios.
32. Conclusion
Saving and retrieving user preferences is an important part of creating personalized React Native applications. By combining React state with persistent storage, developers can remember settings such as theme, language, text size, and notification choices between sessions.
AsyncStorage is a practical option for small, non-sensitive key-value preferences. A reliable implementation should include default values, asynchronous loading, JSON serialization where needed, validation, error handling, and a clear strategy for resetting or migrating saved data. For sensitive information or cross-device synchronization, use an appropriate secure-storage solution or authenticated backend service.
33. Additional Learning Resources