React Native Expo Best Practices for 2025
React Native Expo Best Practices for 2025
React Native with Expo has evolved significantly, making it easier than ever to build cross-platform mobile apps. Here are the best practices we've learned building production apps in 2025.
Expo SDK 52 and Beyond
The latest Expo SDK brings powerful new features:
New Architecture by Default
Expo now uses the new React Native architecture by default, which means:
- Faster performance: Improved rendering pipeline
- Better TypeScript support: Stronger type safety
- Concurrent features: React 19 concurrent rendering
Expo Router v4
The file-based routing system has matured:
// app/(tabs)/profile.tsx
export default function ProfileScreen() {
return <View>...</View>;
}
// Automatic deep linking support
// app/profile/[id].tsx handles /profile/123
Performance Optimization
1. Image Optimization
Always optimize images:
import { Image } from 'expo-image';
// Use expo-image instead of React Native's Image
<Image
source={{ uri: 'https://example.com/image.jpg' }}
placeholder={blurhash}
contentFit="cover"
transition={200}
/>
2. Code Splitting
Use dynamic imports for heavy components:
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
3. List Optimization
For long lists, use FlashList:
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={renderItem}
estimatedItemSize={100}
/>
State Management
Expo Router + Zustand
A lightweight combination:
import create from 'zustand';
const useStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
// Use in any component
function Profile() {
const user = useStore((state) => state.user);
return <Text>{user?.name}</Text>;
}
Server State with TanStack Query
For API data:
import { useQuery } from '@tanstack/react-query';
function Posts() {
const { data, isLoading } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
if (isLoading) return <Loading />;
return <PostsList data={data} />;
}
Type Safety
Strict TypeScript Configuration
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true
}
}
Type-Safe Navigation
import { router } from 'expo-router';
// Type-safe navigation
router.push({
pathname: '/profile/[id]',
params: { id: '123' },
});
Testing Strategy
Unit Tests with Vitest
import { describe, it, expect } from 'vitest';
describe('UserService', () => {
it('should fetch user data', async () => {
const user = await fetchUser('123');
expect(user).toBeDefined();
});
});
Component Tests with React Native Testing Library
import { render, screen } from '@testing-library/react-native';
test('renders user name', () => {
render(<Profile user={{ name: 'John' }} />);
expect(screen.getByText('John')).toBeTruthy();
});
Development Workflow
EAS Build
Use EAS for production builds:
# Development build
eas build --profile development --platform ios
# Production build
eas build --profile production --platform all
Environment Variables
Use expo-constants for environment variables:
import Constants from 'expo-constants';
const API_URL = Constants.expoConfig?.extra?.apiUrl;
Common Pitfalls to Avoid
- Not using Hermes: Always enable Hermes for better performance
- Ignoring bundle size: Monitor and optimize your bundle
- Skipping error boundaries: Implement proper error handling
- Not testing on real devices: Simulators don't catch everything
- Ignoring accessibility: Use accessibility labels and roles
Modern Patterns
Custom Hooks
function useUser(userId: string) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(setUser).finally(() => setLoading(false));
}, [userId]);
return { user, loading };
}
Error Boundaries
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <ErrorScreen />;
}
return this.props.children;
}
}
Conclusion
React Native with Expo in 2025 offers a mature, powerful platform for mobile development. By following these best practices, you'll build apps that are performant, maintainable, and delightful to use.
The key is staying current with Expo's rapid evolution while maintaining solid engineering fundamentals.
