How to share app-wide preferences like measurement units across your Expo app
When building a house hunting app, I ran into a common challenge that many React Native developers face: How do you share global settings across your entire application?
In my case, I needed users to choose between square meters and square feet for property measurements. This preference needed to:
- ✅ Be accessible from any screen
- ✅ Persist between app sessions
- ✅ Update in real-time across components
- ✅ Be type-safe and easy to maintain
Let me show you the elegant solution I implemented using React's Context API and AsyncStorage.
The Problem
Initially, I had a Settings screen with a local useState
hook:
export default function Settings() {
const [metricUnit, setMetricUnit] = useState<'sqm' | 'sqft'>('sqm');
// ... UI code
}
The problem? This state was trapped in the Settings component. Other screens couldn't access it, and the preference would reset every time the app restarted. Not exactly a great user experience.
The Solution: Context API + AsyncStorage
The winning combination uses:
- React Context API - for sharing state across components
- AsyncStorage - for persisting data between sessions
- TypeScript - for type safety
This gives us a global state management solution without adding heavy dependencies like Redux or Zustand.
Step 1: Create the Settings Context
First, create a new file contexts/SettingsContext.tsx
:
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
type MeasurementUnit = 'sqm' | 'sqft';
interface SettingsContextType {
measurementUnit: MeasurementUnit;
setMeasurementUnit: (unit: MeasurementUnit) => Promise<void>;
isLoading: boolean;
}
const SettingsContext = createContext<SettingsContextType | undefined>(undefined);
const STORAGE_KEY = '@househunt_settings';
export function SettingsProvider({ children }: { children: ReactNode }) {
const [measurementUnit, setMeasurementUnitState] = useState<MeasurementUnit>('sqm');
const [isLoading, setIsLoading] = useState(true);
// Load settings from AsyncStorage on mount
useEffect(() => {
loadSettings();
}, []);
const loadSettings = async () => {
try {
const storedSettings = await AsyncStorage.getItem(STORAGE_KEY);
if (storedSettings) {
const settings = JSON.parse(storedSettings);
setMeasurementUnitState(settings.measurementUnit || 'sqm');
}
} catch (error) {
console.error('Error loading settings:', error);
} finally {
setIsLoading(false);
}
};
const setMeasurementUnit = async (unit: MeasurementUnit) => {
try {
setMeasurementUnitState(unit);
const settings = { measurementUnit: unit };
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
} catch (error) {
console.error('Error saving settings:', error);
}
};
return (
<SettingsContext.Provider
value={{
measurementUnit,
setMeasurementUnit,
isLoading,
}}
>
{children}
</SettingsContext.Provider>
);
}
// Custom hook to use the settings context
export function useSettings() {
const context = useContext(SettingsContext);
if (context === undefined) {
throw new Error('useSettings must be used within a SettingsProvider');
}
return context;
}
What's happening here?
- We create a Context with TypeScript types for safety
- The Provider loads settings from AsyncStorage on mount
- When settings change, they're automatically saved to AsyncStorage
- We export a custom
useSettings()
hook for easy consumption
Step 2: Wrap Your App with the Provider
In your root layout file (app/_layout.tsx
in Expo Router):
import { SettingsProvider } from "../contexts/SettingsContext";
import "../global.css";
export default function RootLayout() {
return (
<SettingsProvider>
<Tabs screenOptions={{ /* ... */ }}>
{/* Your screens */}
</Tabs>
</SettingsProvider>
);
}
By wrapping your entire app, every screen can now access the settings context.
Step 3: Update Your Settings Screen
Now update your Settings screen to use the context:
import { useSettings } from "../contexts/SettingsContext";
export default function Settings() {
const { measurementUnit, setMeasurementUnit } = useSettings();
return (
<View className="flex-1 bg-[#FFF9F3] p-5 pt-[60px]">
<Text className="text-[28px] font-bold text-[#765227] mb-[30px]">
Settings
</Text>
<View className="bg-white rounded-xl p-5 shadow-lg">
<Text className="text-base font-semibold text-[#8C6D4A] mb-4">
Area Measurement
</Text>
<TouchableOpacity
className="flex-row items-center py-3"
onPress={() => setMeasurementUnit('sqm')}
>
<View className="w-6 h-6 rounded-full border-2 border-[#8C6D4A] items-center justify-center mr-3">
{measurementUnit === 'sqm' && (
<View className="w-3 h-3 rounded-full bg-[#8C6D4A]" />
)}
</View>
<Text className="text-base text-[#4A3C2B]">
Square Meters (m²)
</Text>
</TouchableOpacity>
<TouchableOpacity
className="flex-row items-center py-3"
onPress={() => setMeasurementUnit('sqft')}
>
<View className="w-6 h-6 rounded-full border-2 border-[#8C6D4A] items-center justify-center mr-3">
{measurementUnit === 'sqft' && (
<View className="w-3 h-3 rounded-full bg-[#8C6D4A]" />
)}
</View>
<Text className="text-base text-[#4A3C2B]">
Square Feet (ft²)
</Text>
</TouchableOpacity>
</View>
</View>
);
}
Notice how clean this is! We simply import useSettings()
and destructure what we need.
Step 4: Use It Anywhere
Now any component in your app can access and use the measurement preference:
import { useSettings } from "../contexts/SettingsContext";
function PropertyCard({ property }) {
const { measurementUnit } = useSettings();
const displayArea = (area: number) => {
if (measurementUnit === 'sqft') {
return `${Math.round(area * 10.764)} ft²`;
}
return `${area} m²`;
};
return (
<View>
<Text>Area: {displayArea(property.area)}</Text>
</View>
);
}
The component automatically re-renders when the measurement unit changes. Magic! ✨
Why This Approach Works
🎯 Simple and Standard
No need to learn Redux or other state management libraries. This uses React's built-in Context API.
💾 Persistent
Settings survive app restarts thanks to AsyncStorage. Users set their preference once and it sticks.
🔒 Type-Safe
TypeScript ensures you can't accidentally use invalid measurement units or forget to wrap a component in the provider.
📈 Scalable
Need to add more settings like currency, theme, or language? Just extend the context:
interface SettingsContextType {
measurementUnit: MeasurementUnit;
currency: Currency;
theme: Theme;
setMeasurementUnit: (unit: MeasurementUnit) => Promise<void>;
setCurrency: (currency: Currency) => Promise<void>;
setTheme: (theme: Theme) => Promise<void>;
}
⚡ Performant
Context changes only trigger re-renders in components that actually use the context. Components that don't care about settings won't re-render.
Bonus: Loading State
Notice the isLoading
flag in the context? This is useful for showing a splash screen while settings load:
function App() {
const { isLoading } = useSettings();
if (isLoading) {
return <SplashScreen />;
}
return <YourApp />;
}
Alternative Approaches
You might be wondering about other options:
Redux/Redux Toolkit - Overkill for simple settings. Adds complexity and boilerplate.
Zustand - Great alternative! Lighter than Redux, but another dependency to manage.
React Query - Excellent for server state, but not ideal for local app preferences.
Props Drilling - Please no. Your code will thank you for not doing this.
Global Variables - They work but don't trigger re-renders and are hard to persist.
For global settings, Context API + AsyncStorage hits the sweet spot of simplicity and functionality.
Wrapping Up
Managing global settings doesn't have to be complicated. With React's Context API and AsyncStorage, you can create a clean, type-safe, persistent solution in under 100 lines of code.
The pattern I've shown works great for:
- User preferences (measurement units, currency, language)
- Theme settings (dark mode, color schemes)
- App configuration (API endpoints, feature flags)
- Authentication state
Give it a try in your next React Native project!
Have you used a different approach for managing global settings? I'd love to hear about it in the comments below. And if you found this helpful, give it a clap! 👏
Building a house hunting app with React Native and Expo. Follow along for more tips on mobile development!
Tags: #ReactNative #Expo #TypeScript #MobileDevelopment #AsyncStorage
Top comments (0)