Loading...
Loading...
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.
npx skill4agent add rakaadi/agent-kit code-refactoringtsconfig.json@components@utils/*@reducers/*@hooks/*@theme@schema/*@types@rootState@config/*@hooks/appuseDispatchuseSelectorcolors.palette.*@themebabel-plugin-react-compiler// Anti-pattern: Nested ternaries and if-else in JSX
return (
<View>
{loading ? (
<Spinner />
) : error ? (
<ErrorView />
) : data ? (
data.length > 0 ? (
<FlatList data={data} />
) : (
<EmptyState />
)
) : null}
</View>
)import { match, P } from 'ts-pattern'
// Pattern matching with ts-pattern (already in dependencies)
const ContentView = match({ loading, error, data })
.with({ loading: true }, () => <Spinner />)
.with({ error: P.not(P.nullish) }, ({ error }) => <ErrorView error={error} />)
.with({ data: P.when(arr => arr && arr.length > 0) }, ({ data }) => (
<FlatList data={data} renderItem={renderItem} />
))
.with({ data: P.array() }, () => <EmptyState />)
.otherwise(() => null)
return <View>{ContentView}</View>// 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
}
}
}// 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
}// Anti-pattern: Inline functions and object literals break memoization
function PatientList({ patients }: Props) {
return (
<FlatList
data={patients}
renderItem={({ item }) => (
<PatientCard
patient={item}
onPress={() => navigate('PatientDetail', { id: item.id })}
style={{ marginBottom: 8 }}
/>
)}
/>
)
}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
}) => (
<PatientCard
patient={patient}
onPress={() => 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 }) => (
<PatientCardItem patient={item} onPress={handlePress} />
), [handlePress])
return (
<FlatList
data={patients}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
)
}
// Extract keyExtractor outside component (stable reference)
const keyExtractor = (item: Patient) => item.id// Anti-pattern: Each card manages its own expanded state
function MedicationList({ medications }: Props) {
return medications.map(med => (
<MedicationCard key={med.id} medication={med} />
))
}
function MedicationCard({ medication }: { medication: Medication }) {
const [expanded, setExpanded] = useState(false)
// Error: When parent re-renders, this state resets
return (
<Pressable onPress={() => setExpanded(!expanded)}>
<Text>{medication.name}</Text>
{expanded && <Text>{medication.dosage}</Text>}
</Pressable>
)
}// Lift state to parent OR use unique keys
function MedicationList({ medications }: Props) {
const [expandedIds, setExpandedIds] = useState<Set<string>>(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 => (
<MedicationCard
key={med.id}
medication={med}
expanded={expandedIds.has(med.id)}
onToggle={toggleExpanded}
/>
))
}
// Pure component with no internal state
const MedicationCard = memo(({
medication,
expanded,
onToggle
}: MedicationCardProps) => (
<Pressable onPress={() => onToggle(medication.id)}>
<Text>{medication.name}</Text>
{expanded && <Text>{medication.dosage}</Text>}
</Pressable>
))// 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 (
<View>
<Text>BP: {patient?.latestVitals?.bloodPressure}</Text>
<Text>HR: {patient?.latestVitals?.heartRate}</Text>
</View>
)
}// 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 (
<View>
<Text>BP: {vitals?.bloodPressure ?? '-'}</Text>
<Text>HR: {vitals?.heartRate ?? '-'}</Text>
</View>
)
}// Anti-pattern: Fetches even when ID is undefined
function PatientDetails({ patientId }: { patientId?: string }) {
const { data, isLoading } = useGetPatientQuery(patientId ?? '')
if (!patientId) return <Text>Select a patient</Text>
// Query already executed with empty string
}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 <Text>Select a patient</Text>
if (isLoading) return <ActivityIndicator />
return <PatientCard patient={data} />
}// 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
}// Properly configured mutation invalidates cache automatically
const vitalsApi = mainApi.injectEndpoints({
endpoints: builder => ({
updateVitals: builder.mutation<void, UpdateVitalsParams>({
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<Patient, string>({
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
}// Anti-pattern: Continuous polling even when screen is hidden
function LiveMonitorScreen({ patientId }: Props) {
const { data } = useGetVitalsQuery(patientId, {
pollingInterval: 5000 // Polls forever
})
}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 <VitalsDisplay vitals={data} />
}// 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
}// Direct calculation (React Compiler optimizes this)
function DosageCalculator({ weight, medication }: Props) {
const dosage = weight * medication.dosePerKg
return <Text>{dosage}mg</Text>
}// 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 <Text>{fullName}</Text>
}// Compute during render (no synchronization needed)
function PatientSummary({ patient }: Props) {
const fullName = `${patient.firstName} ${patient.lastName}`
return <Text>{fullName}</Text>
}// Anti-pattern: Wrapping stable functions
function FormScreen() {
const navigation = useNavigation()
const goBack = useCallback(() => {
navigation.goBack()
}, [navigation])
// navigation is already stable from React Navigation
}// Direct inline handler (React Compiler handles this)
function FormScreen() {
const navigation = useNavigation()
return (
<Button onPress={() => navigation.goBack()}>
Cancel
</Button>
)
}// Old pattern: useEffect + loading state
function PatientLoader({ patientId }: Props) {
const [patient, setPatient] = useState<Patient | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchPatient(patientId).then(data => {
setPatient(data)
setLoading(false)
})
}, [patientId])
if (loading) return <Spinner />
return <PatientCard patient={patient} />
}import { use, Suspense } from 'react'
// use() unwraps promises and integrates with Suspense
function PatientLoader({ patientPromise }: { patientPromise: Promise<Patient> }) {
const patient = use(patientPromise)
return <PatientCard patient={patient} />
}
// Parent component
function PatientScreen({ patientId }: Props) {
const patientPromise = fetchPatient(patientId)
return (
<Suspense fallback={<Spinner />}>
<PatientLoader patientPromise={patientPromise} />
</Suspense>
)
}// Old pattern: useState + async handler
function VitalsForm({ patientId }: Props) {
const [pending, setPending] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleSubmit = async (data: VitalsInput) => {
setPending(true)
setError(null)
try {
await submitVitals(patientId, data)
} catch (err) {
setError(err.message)
} finally {
setPending(false)
}
}
}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 (
<form action={submitAction}>
{state.error && <Text>{state.error}</Text>}
<Button disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</Button>
</form>
)
}// Old pattern: useEffect for DOM operations
function VideoPlayer({ src }: Props) {
const videoRef = useRef<Video>(null)
useEffect(() => {
const player = videoRef.current
if (player) {
player.play()
return () => player.pause()
}
}, [])
return <Video ref={videoRef} source={src} />
}// ref callback with cleanup function
function VideoPlayer({ src }: Props) {
return (
<Video
ref={(player) => {
if (player) {
player.play()
return () => player.pause() // Cleanup
}
}}
source={src}
/>
)
}selectFromResultskipTokeninvalidatesTagsprovidesTagsuseFocusEffectuseMemouseCallbackuseEffectts-patternuseCallback@components@utils/*colors.palette.*@themeuseAppDispatchuseAppSelector../../componentscolors.palette.*SuspenseuseDispatchuseSelectorts-patternimport { match, P } from 'ts-pattern'
import { useCallback, useState, memo } from 'react'
import { useGetPatientsQuery } from '@reducers/patientSlice'
import { colors } from '@theme'
// Lift state to parent so it survives re-renders
function PatientList() {
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
const { patients, isLoading, error } = useGetPatientsQuery(undefined, {
// Only re-render when patients array changes, not when unrelated data updates
selectFromResult: ({ data, isLoading, error }) => ({
patients: data?.patients ?? [],
isLoading,
error
})
})
// Stable callback for toggling expansion
const toggleExpanded = useCallback((id: string) => {
setExpandedIds(prev => {
const next = new Set(prev)
next.has(id) ? next.delete(id) : next.add(id)
return next
})
}, [])
// Clear, readable state matching with ts-pattern
const Content = match({ isLoading, error, patients })
.with({ isLoading: true }, () => <ActivityIndicator />)
.with({ error: P.not(P.nullish) }, ({ error }) => (
<ErrorView message={error.message} />
))
.with({ patients: P.when(arr => arr.length > 0) }, ({ patients }) => (
<FlatList
data={patients}
renderItem={({ item }) => (
<PatientCard
patient={item}
expanded={expandedIds.has(item.id)}
onToggle={toggleExpanded}
/>
)}
keyExtractor={keyExtractor}
/>
))
.with({ patients: P.array() }, () => (
<EmptyState message="No patients found" />
))
.otherwise(() => null)
return <View style={styles.container}>{Content}</View>
}
// Memoized to prevent re-renders when other list items change
const PatientCard = memo(({ patient, expanded, onToggle }: PatientCardProps) => (
<Pressable
onPress={() => onToggle(patient.id)}
style={styles.card}
>
<Text style={styles.name}>{patient.name}</Text>
{expanded && (
<Text style={styles.details}>
Age: {patient.age} | MRN: {patient.mrn}
</Text>
)}
</Pressable>
))
// Extract keyExtractor outside component for stable reference
const keyExtractor = (item: Patient) => item.id
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.palette.white
},
card: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: colors.palette.neutral300
},
name: {
fontSize: 16,
fontFamily: 'Manrope-SemiBold',
color: colors.palette.neutral900
},
details: {
marginTop: 8,
fontSize: 14,
fontFamily: 'Manrope-Regular',
color: colors.palette.neutral600
}
})ts-patternselectFromResulttoggleExpandedkeyExtractor