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/let only, === 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

  • FlashList over FlatList for lists > ~20 items
  • useMemo for expensive computations, useCallback for callbacks passed as props
  • React.memo for pure display components
  • Always paginate Supabase queries with .range()