---
name: code-refactoring
description: Guide for code refactoring, use this skill to guide you when user asked to refactor a components or functions and when an implementation of a plan requiring a code refactoring.
---
# Ecalyptus React Native Refactoring Skill
## Skill Purpose
This skill specializes in refactoring React Native components within the Ecalyptus healthcare mobile application. It focuses on eliminating deeply nested conditionals, optimizing array operations and component re-renders, improving RTK Query usage patterns, and enhancing code readability while maintaining React Compiler compatibility and ESLint compliance.
## Core Refactoring Principles
**Priority Order (Highest to Lowest):**
1. Code readability and maintainability
2. RTK Query optimization (data flow determines component architecture)
3. Logical improvements and proper React patterns
4. Performance optimizations (let React Compiler handle micro-optimizations)
5. Modern React patterns (when they genuinely improve clarity)
**Non-Negotiable Rules:**
- Always use path aliases from `tsconfig.json` (`@components`, `@utils/*`, `@reducers/*`, `@hooks/*`, `@theme`, `@schema/*`, `@types`, `@rootState`, `@config/*`)
- Use typed Redux hooks from `@hooks/app` (never plain `useDispatch`/`useSelector`)
- Reference colors via `colors.palette.*` from `@theme` (no hardcoded hex values)
- Maintain React Compiler compatibility (enabled via `babel-plugin-react-compiler`)
- Follow Rules of Hooks (no conditional hook calls, proper dependency arrays)
- Support React Native environment (verify Web-only APIs before suggesting)
## Refactoring Targets
### 1. Deeply Nested Conditionals
**Problem Pattern - Rendering Logic:**
```typescript
// Anti-pattern: Nested ternaries and if-else in JSX
return (
{loading ? (
) : error ? (
) : data ? (
data.length > 0 ? (
) : (
)
) : null}
)
```
**Refactored Solution:**
```typescript
import { match, P } from 'ts-pattern'
// Pattern matching with ts-pattern (already in dependencies)
const ContentView = match({ loading, error, data })
.with({ loading: true }, () => )
.with({ error: P.not(P.nullish) }, ({ error }) => )
.with({ data: P.when(arr => arr && arr.length > 0) }, ({ data }) => (
))
.with({ data: P.array() }, () => )
.otherwise(() => null)
return {ContentView}
```
**Problem Pattern - Business Logic:**
```typescript
// Anti-pattern: Nested if-else in functions
function calculateDosage(patient: Patient, medication: Medication) {
if (patient.age < 18) {
if (patient.weight < 50) {
if (medication.type === 'antibiotic') {
return medication.baseDose * 0.5
} else {
return medication.baseDose * 0.7
}
} else {
return medication.baseDose * 0.8
}
} else {
if (patient.hasKidneyDisease) {
return medication.baseDose * 0.6
} else {
return medication.baseDose
}
}
}
```
**Refactored Solution:**
```typescript
// Early returns + extracted logic
function calculateDosage(patient: Patient, medication: Medication) {
// Handle adult patients with kidney disease first
if (patient.age >= 18) {
return patient.hasKidneyDisease
? medication.baseDose * 0.6
: medication.baseDose
}
// Pediatric dosage calculation
return calculatePediatricDosage(patient, medication)
}
function calculatePediatricDosage(patient: Patient, medication: Medication) {
const weightFactor = patient.weight < 50 ? 0.5 : 0.8
const typeFactor = medication.type === 'antibiotic' && patient.weight < 50 ? 1.0 : 1.4
return medication.baseDose * weightFactor * typeFactor
}
```
### 2. Array Mapping Optimization
**Problem Pattern - Unstable References:**
```typescript
// Anti-pattern: Inline functions and object literals break memoization
function PatientList({ patients }: Props) {
return (
(
navigate('PatientDetail', { id: item.id })}
style={{ marginBottom: 8 }}
/>
)}
/>
)
}
```
**Refactored Solution:**
```typescript
import { useCallback, useMemo } from 'react'
import { StyleSheet } from 'react-native-unistyles'
// Stable styles object (created once)
const styles = StyleSheet.create({
card: { marginBottom: 8 }
})
// Memoized item component (prevents re-renders)
const PatientCardItem = memo(({
patient,
onPress
}: {
patient: Patient
onPress: (id: string) => void
}) => (
onPress(patient.id)}
style={styles.card}
/>
))
function PatientList({ patients }: Props) {
const navigation = useNavigation()
// Stable callback reference
const handlePress = useCallback((id: string) => {
navigation.navigate('PatientDetail', { id })
}, [navigation])
// Stable render function
const renderItem = useCallback(({ item }: { item: Patient }) => (
), [handlePress])
return (
)
}
// Extract keyExtractor outside component (stable reference)
const keyExtractor = (item: Patient) => item.id
```
**Problem Pattern - Local State in Mapped Components:**
```typescript
// Anti-pattern: Each card manages its own expanded state
function MedicationList({ medications }: Props) {
return medications.map(med => (
))
}
function MedicationCard({ medication }: { medication: Medication }) {
const [expanded, setExpanded] = useState(false)
// Error: When parent re-renders, this state resets
return (
setExpanded(!expanded)}>
{medication.name}
{expanded && {medication.dosage}}
)
}
```
**Refactored Solution:**
```typescript
// Lift state to parent OR use unique keys
function MedicationList({ medications }: Props) {
const [expandedIds, setExpandedIds] = useState>(new Set())
const toggleExpanded = useCallback((id: string) => {
setExpandedIds(prev => {
const next = new Set(prev)
next.has(id) ? next.delete(id) : next.add(id)
return next
})
}, [])
return medications.map(med => (
))
}
// Pure component with no internal state
const MedicationCard = memo(({
medication,
expanded,
onToggle
}: MedicationCardProps) => (
onToggle(medication.id)}>
{medication.name}
{expanded && {medication.dosage}}
))
```
### 3. RTK Query Hook Optimization
**Problem Pattern - Over-Fetching Data:**
```typescript
// Anti-pattern: Component re-renders when ANY field in patient changes
function VitalSignsDisplay({ patientId }: Props) {
const { data: patient } = useGetPatientQuery(patientId)
// Re-renders even when only patient.name changes (not vitals)
return (
BP: {patient?.latestVitals?.bloodPressure}
HR: {patient?.latestVitals?.heartRate}
)
}
```
**Refactored Solution:**
```typescript
// Cherry-pick only needed fields with selectFromResult
function VitalSignsDisplay({ patientId }: Props) {
const { vitals } = useGetPatientQuery(patientId, {
selectFromResult: ({ data }) => ({
vitals: data?.latestVitals
})
})
// Only re-renders when latestVitals actually changes
return (
BP: {vitals?.bloodPressure ?? '-'}
HR: {vitals?.heartRate ?? '-'}
)
}
```
**Problem Pattern - Conditional Fetching:**
```typescript
// Anti-pattern: Fetches even when ID is undefined
function PatientDetails({ patientId }: { patientId?: string }) {
const { data, isLoading } = useGetPatientQuery(patientId ?? '')
if (!patientId) return Select a patient
// Query already executed with empty string
}
```
**Refactored Solution:**
```typescript
import { skipToken } from '@reduxjs/toolkit/query'
// Use skipToken to prevent unnecessary requests
function PatientDetails({ patientId }: { patientId?: string }) {
const { data, isLoading } = useGetPatientQuery(patientId ?? skipToken)
if (!patientId) return Select a patient
if (isLoading) return
return
}
```
**Problem Pattern - Missing Cache Invalidation:**
```typescript
// Anti-pattern: Manual refetch after mutation
const [updateVitals] = useUpdateVitalsMutation()
const { refetch } = useGetPatientQuery(patientId)
const handleSubmit = async (vitals: VitalsInput) => {
await updateVitals({ patientId, vitals })
refetch() // Manual refetch is fragile
}
```
**Refactored Solution:**
```typescript
// Properly configured mutation invalidates cache automatically
const vitalsApi = mainApi.injectEndpoints({
endpoints: builder => ({
updateVitals: builder.mutation({
query: ({ patientId, vitals }) => ({
url: `/patients/${patientId}/vitals`,
method: 'POST',
body: vitals
}),
// Automatic cache invalidation
invalidatesTags: (result, error, { patientId }) => [
{ type: 'Patient', id: patientId },
'PatientVitals'
]
}),
getPatient: builder.query({
query: (id) => `/patients/${id}`,
providesTags: (result, error, id) => [
{ type: 'Patient', id }
]
})
})
})
// Usage - no manual refetch needed
const [updateVitals] = useUpdateVitalsMutation()
const handleSubmit = async (vitals: VitalsInput) => {
await updateVitals({ patientId, vitals })
// Cache automatically refetches due to invalidatesTags
}
```
**Problem Pattern - Polling Without Cleanup:**
```typescript
// Anti-pattern: Continuous polling even when screen is hidden
function LiveMonitorScreen({ patientId }: Props) {
const { data } = useGetVitalsQuery(patientId, {
pollingInterval: 5000 // Polls forever
})
}
```
**Refactored Solution:**
```typescript
import { useFocusEffect } from '@react-navigation/native'
import { useRef } from 'react'
function LiveMonitorScreen({ patientId }: Props) {
const [pollingInterval, setPollingInterval] = useState(0)
// Start/stop polling based on screen focus
useFocusEffect(
useCallback(() => {
setPollingInterval(5000)
return () => setPollingInterval(0)
}, [])
)
const { data } = useGetVitalsQuery(patientId, {
pollingInterval,
skip: !pollingInterval // Don't fetch when interval is 0
})
return
}
```
### 4. Unnecessary Hook Usage
**Problem Pattern - Redundant useMemo:**
```typescript
// Anti-pattern: Memoizing primitive calculations
function DosageCalculator({ weight, medication }: Props) {
const dosage = useMemo(() => {
return weight * medication.dosePerKg
}, [weight, medication.dosePerKg])
// Simple arithmetic doesn't need memoization
}
```
**Refactored Solution:**
```typescript
// Direct calculation (React Compiler optimizes this)
function DosageCalculator({ weight, medication }: Props) {
const dosage = weight * medication.dosePerKg
return {dosage}mg
}
```
**Problem Pattern - useEffect for Derived State:**
```typescript
// Anti-pattern: Synchronizing state with useEffect
function PatientSummary({ patient }: Props) {
const [fullName, setFullName] = useState('')
useEffect(() => {
setFullName(`${patient.firstName} ${patient.lastName}`)
}, [patient.firstName, patient.lastName])
return {fullName}
}
```
**Refactored Solution:**
```typescript
// Compute during render (no synchronization needed)
function PatientSummary({ patient }: Props) {
const fullName = `${patient.firstName} ${patient.lastName}`
return {fullName}
}
```
**Problem Pattern - useCallback Without Benefit:**
```typescript
// Anti-pattern: Wrapping stable functions
function FormScreen() {
const navigation = useNavigation()
const goBack = useCallback(() => {
navigation.goBack()
}, [navigation])
// navigation is already stable from React Navigation
}
```
**Refactored Solution:**
```typescript
// Direct inline handler (React Compiler handles this)
function FormScreen() {
const navigation = useNavigation()
return (
)
}
```
### 5. Modern React Patterns (React 19+)
**Pattern - use() Hook for Promises:**
```typescript
// Old pattern: useEffect + loading state
function PatientLoader({ patientId }: Props) {
const [patient, setPatient] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchPatient(patientId).then(data => {
setPatient(data)
setLoading(false)
})
}, [patientId])
if (loading) return
return
}
```
**Modern Solution (React 19):**
```typescript
import { use, Suspense } from 'react'
// use() unwraps promises and integrates with Suspense
function PatientLoader({ patientPromise }: { patientPromise: Promise }) {
const patient = use(patientPromise)
return
}
// Parent component
function PatientScreen({ patientId }: Props) {
const patientPromise = fetchPatient(patientId)
return (
}>
)
}
```
**Pattern - useActionState for Forms (React 19):**
```typescript
// Old pattern: useState + async handler
function VitalsForm({ patientId }: Props) {
const [pending, setPending] = useState(false)
const [error, setError] = useState(null)
const handleSubmit = async (data: VitalsInput) => {
setPending(true)
setError(null)
try {
await submitVitals(patientId, data)
} catch (err) {
setError(err.message)
} finally {
setPending(false)
}
}
}
```
**Modern Solution (React 19):**
```typescript
import { useActionState } from 'react'
// useActionState handles pending state and errors automatically
function VitalsForm({ patientId }: Props) {
const [state, submitAction, isPending] = useActionState(
async (prevState: FormState, formData: FormData) => {
try {
await submitVitals(patientId, formData)
return { success: true, error: null }
} catch (err) {
return { success: false, error: err.message }
}
},
{ success: false, error: null }
)
return (
)
}
```
**Pattern - ref Callback Cleanup (React 19):**
```typescript
// Old pattern: useEffect for DOM operations
function VideoPlayer({ src }: Props) {
const videoRef = useRef