Code Quality
npm run lint
npm run lint:fix
npx tsc --noEmit
Mandatory Rules
No inline styles in screens
// Correct:
<View className="flex-1 bg-gray-50 px-4 py-6">
<Text className="text-xl font-bold">Title</Text>
</View>
// Wrong: no StyleSheet.create or style props in app/ screens
<View style={{ flex: 1, padding: 16 }}>
No any
// Correct:
function handleError(error: ApiError): void {}
// Wrong:
function handleError(error: any): void {}
No direct Supabase/Azure calls in screens
// Correct: call a service
import { getCases } from '@/src/services/caseService';
// Wrong: screen imports supabase directly
import { supabase } from '@/src/lib/supabaseClient';
No bare console.log
if (__DEV__) { console.log('[Screen] data:', data); } // Correct: stripped in prod
console.error('[service] failed:', error); // Correct: always acceptable
console.log('debug', data); // Wrong
const/letonly,===always
ESLint
Config files: eslint.config.js / eslint.config.mjs
TypeScript
Strict mode is on in tsconfig.json using "strict": true
// Always type params and returns
async function getCaseById(id: string): Promise<Case | null> {
const { data, error } = await supabase.from('cases').select('*').eq('id', id).single();
if (error) return null;
return data;
}
// Shared types in src/types/index.ts
export interface Case {
id: string;
title: string;
status: 'open' | 'in_progress' | 'closed';
client_id: string;
assigned_to: string;
created_at: string;
}
// Handle null explicitly
const { id } = useLocalSearchParams<{ id: string }>();
if (!id) return <ErrorState message="Missing ID" />;
Naming Conventions
// Components, types: PascalCase
function CaseCard() {}
interface CaseCardProps {}
type UserRole = 'admin' | 'practitioner';
// Variables, functions: camelCase
const isLoading = true;
function handleSubmit() {}
// Module-level constants: UPPER_SNAKE_CASE
const MAX_RECORDING_DURATION_MS = 3600000;
// Booleans: is/has/should/can prefix
const isAuthenticated = true;
// Event handlers: handle prefix
const handlePress = () => {};
File Structure
// 1. External imports (React, RN, Expo)
// 2. Internal imports (services, components, types)
// 3. Local types/interfaces
// 4. Component: hooks → effects → callbacks → render
Error Handling
Services log and rethrow. Screens catch and show user-facing state.
// service
export async function updateCase(id: string, status: Case['status']): Promise<void> {
const { error } = await supabase.from('cases').update({ status }).eq('id', id);
if (error) {
console.error('[caseService]', error.message);
throw error;
}
}
// screen
try {
await updateCase(caseId, 'closed');
router.back();
} catch {
setError('Failed to update. Please try again.');
}
DRY(Dont Repeat Yourself)
If the same UI appears in more than one screen
Extract it into src/components/ui/(generalization)
// Wrong: duplicated in cases.tsx and clients.tsx
<View className="flex-row items-center justify-between px-4 py-3 bg-white border-b">
<Text className="text-lg font-bold">Cases</Text>
</View>
// Correct: extracted once
<Header title="Cases" actionLabel="Filter" onActionPress={handleFilter} />
Performance
FlashListoverFlatListfor lists > ~20 itemsuseMemofor expensive computations,useCallbackfor callbacks passed as propsReact.memofor pure display components- Always paginate Supabase queries with
.range()