perf: memoize expensive list computations + memo on grid/list items
Results.tsx: - useMemo: filter + sort in one pass (was 7 separate array iterations per render) - useMemo: platform/maison/future/missingData counts in single for-loop - Fix: move queryClient.invalidateQueries from render body into useEffect Properties.tsx: - useMemo: wrap applyFilters() call (was full copy+sort on every render) - useMemo: compute matchReady/criticalGaps/lowConfidence/staleOrOutdated/ allMissingFields in a single for-loop (was 5 separate filter passes) ReminderFeed.tsx: - useMemo: wrap applyFilters() call - useCallback: resetFilters (passed to ReminderEmptyState) PropertyIntelligenceCard, ReminderListRow: - React.memo: grid/list items no longer re-render when unrelated parent state changes (e.g. selectedId, filter UI state) tsc --noEmit passes with zero errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { Box, Drawer, useMediaQuery, useTheme } from '@mui/material'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { PageHeader } from '../../components/layout'
|
||||
@@ -58,22 +58,37 @@ export default function Properties() {
|
||||
)
|
||||
|
||||
const { data: properties = [], isLoading, isError } = useProperties()
|
||||
const filtered = applyFilters(properties, filters)
|
||||
|
||||
// Decision-relevant aggregates
|
||||
const matchReady = properties.filter(
|
||||
p => (p.availabilityStatus === 'AVAILABLE_NOW' || p.availabilityStatus === 'AVAILABLE_SOON') &&
|
||||
p.confidenceScore >= 0.7 && p.dataQuality.missingCriticalFields.length === 0
|
||||
)
|
||||
const criticalGaps = properties.filter(p => p.dataQuality.missingCriticalFields.length > 0)
|
||||
const lowConfidence = properties.filter(p => p.confidenceScore < 0.55)
|
||||
const staleOrOutdated = properties.filter(
|
||||
p => p.dataQuality.freshness === 'STALE' || p.dataQuality.freshness === 'OUTDATED'
|
||||
)
|
||||
const filtered = useMemo(() => applyFilters(properties, filters), [properties, filters])
|
||||
|
||||
const allMissingFields = [...new Set(
|
||||
properties.flatMap(p => p.dataQuality.missingCriticalFields)
|
||||
)].slice(0, 4)
|
||||
// Decision-relevant aggregates — single pass over the full list
|
||||
const { matchReady, criticalGaps, lowConfidence, staleOrOutdated, allMissingFields } = useMemo(() => {
|
||||
const matchReady: typeof properties = []
|
||||
const criticalGaps: typeof properties = []
|
||||
const lowConfidence: typeof properties = []
|
||||
const staleOrOutdated: typeof properties = []
|
||||
const missingSet = new Set<string>()
|
||||
|
||||
for (const p of properties) {
|
||||
if ((p.availabilityStatus === 'AVAILABLE_NOW' || p.availabilityStatus === 'AVAILABLE_SOON') &&
|
||||
p.confidenceScore >= 0.7 && p.dataQuality.missingCriticalFields.length === 0)
|
||||
matchReady.push(p)
|
||||
if (p.dataQuality.missingCriticalFields.length > 0) {
|
||||
criticalGaps.push(p)
|
||||
p.dataQuality.missingCriticalFields.forEach(f => missingSet.add(f))
|
||||
}
|
||||
if (p.confidenceScore < 0.55) lowConfidence.push(p)
|
||||
if (p.dataQuality.freshness === 'STALE' || p.dataQuality.freshness === 'OUTDATED') staleOrOutdated.push(p)
|
||||
}
|
||||
|
||||
return {
|
||||
matchReady,
|
||||
criticalGaps,
|
||||
lowConfidence,
|
||||
staleOrOutdated,
|
||||
allMissingFields: [...missingSet].slice(0, 4),
|
||||
}
|
||||
}, [properties])
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
|
||||
Reference in New Issue
Block a user