Project standards and best practices for maintaining consistency.
- Components: PascalCase -
Button.tsx,UserProfile.tsx - Hooks: camelCase with
useprefix -useFetch.ts,useToggle.ts - Services/Utils: camelCase -
api.ts,storage.ts,formatDate.ts - Types: camelCase -
api.ts,user.ts - Constants: PascalCase -
Colors.ts,Theme.ts - Screens: PascalCase (Expo Router auto-converts) -
index.tsx→/,profile.tsx→/profile
Organize imports in this order:
- React and React Native
- Third-party libraries
- Expo packages
- Local absolute imports (
@/) - Relative imports (
./,../) - Types (with
typekeyword)
Example:
import { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Button, Text } from 'react-native-paper';
import { SafeAreaView } from 'react-native-safe-area-context';
import { router } from 'expo-router';
import { useFetch } from '@/hooks/useFetch';
import { userApi } from '@/services/api';
import { LoadingScreen } from '@/components/LoadingScreen';
import type { User } from '@/types/api';Follow this structure for components:
// 1. Imports
import { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
// 2. Types/Interfaces (if needed)
interface Props {
title: string;
onPress: () => void;
}
// 3. Component
export function CustomButton({ title, onPress }: Props) {
// 4. Hooks
const [loading, setLoading] = useState(false);
// 5. Handlers
const handlePress = async () => {
setLoading(true);
await onPress();
setLoading(false);
};
// 6. Render
return (
<Button onPress={handlePress} loading={loading}>
{title}
</Button>
);
}
// 7. Styles (at bottom)
const styles = StyleSheet.create({
container: {
flex: 1,
},
});Always type function parameters and return values:
// Good
function getUser(id: number): Promise<User> {
return userApi.getById(id);
}
// Avoid
function getUser(id) {
return userApi.getById(id);
}Use interfaces for object shapes:
interface User {
id: number;
name: string;
email: string;
}Use type for unions and intersections:
type Status = 'loading' | 'success' | 'error';
type UserWithRole = User & { role: string };Prefer interface for extensible types, type for unions/intersections.
Document public APIs:
/**
* Fetches user data with automatic loading and error handling.
* @param userId - The user ID to fetch
* @returns Object with data, loading, error, and refetch function
*/
export function useUser(userId: number) {
// Implementation
}Explain "why", not "what":
// Good: Explains reasoning
// Use ref to avoid stale closure in async callback
const mountedRef = useRef(true);
// Avoid: States the obvious
// Set loading to true
setLoading(true);Remove commented-out code - Use git history instead.
Keep related files together:
components/
Button/
Button.tsx
Button.test.tsx
index.ts
Or flat structure for simple components:
components/
Button.tsx
Card.tsx
LoadingScreen.tsx
Group by feature when appropriate:
features/
auth/
LoginScreen.tsx
authService.ts
types.ts
todos/
TodosScreen.tsx
todosApi.ts
types.ts
Current structure (starter template):
app/- Screens (Expo Router)components/- Reusable UI componentshooks/- Custom React hooksservices/- API and storagetypes/- TypeScript definitionsconstants/- App constants
Use functional components with hooks:
// Good
export function UserCard({ user }: { user: User }) {
return <Text>{user.name}</Text>;
}
// Avoid class componentsExtract complex logic to custom hooks:
// Good
function UserScreen({ userId }: { userId: number }) {
const { data, loading } = useUser(userId);
// Render
}
// Avoid: Complex logic in component
function UserScreen({ userId }: { userId: number }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// 50 lines of fetch logic...
}Use StyleSheet.create for performance:
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
});Use theme colors from React Native Paper:
import { useTheme } from 'react-native-paper';
const theme = useTheme();
<View style={{ backgroundColor: theme.colors.background }} />;Avoid inline styles for complex objects - Use StyleSheet.
Always handle async errors:
// Good
try {
const data = await api.getData();
} catch (error) {
console.error('Failed to fetch:', error);
// Show user-friendly error
}
// Avoid
const data = await api.getData(); // Unhandled promise rejectionUse error boundaries for component errors:
<ErrorBoundary>
<RiskyComponent />
</ErrorBoundary>Name test files: ComponentName.test.tsx or ComponentName.spec.tsx
Use descriptive test names:
describe('useFetch', () => {
it('should return loading state initially', () => {
// Test
});
});Format: type: description
Types:
feat:- New featurefix:- Bug fixdocs:- Documentationstyle:- Formattingrefactor:- Code restructuringtest:- Testschore:- Maintenance
Example: feat: add user profile screen
- Files: PascalCase for components, camelCase for utilities
- Imports: React → Libraries → Local → Types
- Components: Imports → Types → Component → Styles
- TypeScript: Type everything, prefer interfaces for objects
- Comments: Document public APIs, explain "why"
- Organization: Group by feature or keep flat
- Patterns: Functional components, extract logic to hooks
- Styling: StyleSheet.create, use theme colors
- Errors: Always handle async errors
Follow these conventions for consistency and maintainability.
- Getting Started - Project setup
- How-To Guides - Common tasks
- React Native Paper Docs - Component library