Spaces:
Sleeping
App.tsx Refactoring Plan: Extract Custom Hooks
Status: SHIPPED (historical plan). Both refactor waves have landed and this document is preserved as the commit-trail log, not a live plan. App.tsx is now ~1400 lines (down from 2100; the post-0.7.0 hooks
useOverflowIframe/useDiagramHighlightsand the design-token migration nudged it back up from the 1150 floor reached after PR #109). It is the state-orchestration hub described infrontend/CLAUDE.md. For the current architecture, start withfrontend/CLAUDE.md; for the latest extraction round (Phase 2 hook extraction —useN1Fetch,useDiagramHighlights,AppSidebar,SidebarSummary,StatusToasts) see the App.tsx refactor-history table at the bottom of that file and the 0.6.5 / 0.7.0 entries inCHANGELOG.md.Completed waves:
- Phase 1 (PR #74, committed) — extract the five pre-written hooks listed below, plus presentational components.
- Phase 2 hook extraction (PR #109, commit
5869ad1) — split the N-1 fetch pipeline intouseN1Fetch, the per-tab SVG highlight pipeline intouseDiagramHighlights, and the sidebar layout intoAppSidebar/SidebarSummary/StatusToasts. ReducedApp.tsxfrom 1575 to ~1150 lines.Deferred (explicitly scoped out of PR #109 — see the refactor history table in
frontend/CLAUDE.md): orchestrator hooks (useSettingsOrchestration,useSaveLoadSession,useStateReset),AppContext-provider conversion, and the NDJSON parser extraction forhandleSimulateUnsimulatedAction.
Goal (as of Phase 1)
Refactor the 2100-line App.tsx into a lean orchestrator by wiring
up 5 pre-written custom hooks that extract state and logic.
Initial State (as of Phase 1 start)
- 5 hook files exist in
frontend/src/hooks/(untracked):useSettings.ts(253 lines) - paths, recommender params, settings modaluseActions.ts(121 lines) - action selection/rejection/favorite stateuseAnalysis.ts(219 lines) - result, overloads, run-analysis flowuseDiagrams.ts(660 lines) - diagrams, pan/zoom, SLD, voltage filter, zoom-to-elementuseSession.ts(338 lines) - save/restore session
usePanZoom.ts(288 lines) - already committed and used by both App.tsx and useDiagrams- Type imports in all 5 hooks have been fixed (
React.Dispatch->Dispatch, etc.) useSettingshas been partially integrated into App.tsx (Step 2 done)- App.tsx then compiled clean at ~1959 lines.
Hook Interfaces Summary
useSettings() -> SettingsState
Provides: networkPath, actionPath, layoutPath, outputFolderPath + setters; all recommender params + setters; monitoring settings; actionDict info; settings modal state; pickSettingsPath, handleOpenSettings, handleCloseSettings, buildConfigRequest, applyConfigResponse, createCurrentBackup
useActions() -> ActionsState
Provides: selectedActionIds, manuallyAddedIds, rejectedActionIds, suggestedByRecommenderIds + setters; handleActionFavorite(actionId, setResult), handleActionReject(actionId), handleManualActionAdded(actionId, detail, linesOverloaded, setResult, onSelectAction), clearActionState()
useAnalysis() -> AnalysisState
Provides: result/setResult, pendingAnalysisResult, analysisLoading, infoMessage, error + setters; selectedOverloads, monitorDeselected; prevResultRef; handleRunAnalysis(selectedBranch, clearContingencyState, setSuggestedByRecommenderIds), handleDisplayPrioritizedActions(selectedActionIds), handleToggleOverload(overload)
Note: handleRunAnalysis does NOT call setActiveTab('overflow') on PDF event (the original App.tsx does). App.tsx must add an effect to handle this.
useDiagrams(branches, voltageLevels) -> DiagramsState
Provides: activeTab, diagrams (n/n1/action), selectedActionId, actionViewMode, viewBox, inspectQuery, SVG container refs, pan/zoom instances, metadata indices, voltage filter state, vlOverlay, branch refs (committedBranchRef, restoringSessionRef, lastZoomState, actionSyncSourceRef); fetchBaseDiagram, handleActionSelect(actionId, result, selectedBranch, vlLength, setResult, setError), zoom handlers, SLD handlers, handleAssetClick(actionId, assetName, tab, selectedActionId, handleActionSelectFn), zoomToElement, inspectableItems, selectedBranchForSld ref
useSession() -> SessionState
Provides: showReloadModal, sessionList, sessionListLoading, sessionRestoring; handleSaveResults(SaveParams), handleOpenReloadModal(outputFolderPath, setError), handleRestoreSession(sessionName, RestoreContext)
Decomposed Steps
Step 1: Add hook imports (DONE)
Added import lines for all 5 hooks at top of App.tsx.
Step 2: Integrate useSettings (DONE)
- Instantiated
useSettings()and destructured all properties - Removed old useState declarations for settings
- Removed old
handleOpenSettings,handleCloseSettings,pickSettingsPath - Removed localStorage load/persist effects
- Updated
handleApplySettingsandhandleLoadConfigto usebuildConfigRequest()andapplyConfigResponse()
Step 3: Integrate useActions
Remove from App.tsx:
useStatefor: selectedActionIds, manuallyAddedIds, rejectedActionIds, suggestedByRecommenderIdshandleActionFavorite,handleActionRejectcallbacks
Add:
const actionsHook = useActions();and destructure- Wrapper for
handleManualActionAddedthat passessetResultandwrappedActionSelect - Wrapper for
handleActionFavoritethat passessetResult
Step 4: Integrate useAnalysis
Remove from App.tsx:
useStatefor: result, pendingAnalysisResult, analysisLoading, infoMessage, error, selectedOverloads, monitorDeselectedprevResultRefinfoMessageauto-clear effecthandleRunAnalysis,handleDisplayPrioritizedActions,handleToggleOverloadcallbacks
Add:
const analysis = useAnalysis();and destructure- Wrapper for
handleRunAnalysisthat passesselectedBranch,clearContingencyState,setSuggestedByRecommenderIds - Wrapper for
handleDisplayPrioritizedActionsthat passesselectedActionIds - Effect: when
result?.pdf_urlchanges duringanalysisLoading, callsetActiveTab('overflow')
Update:
clearContingencyStateto call analysis setters +actionsHook.clearActionState()hasAnalysisStateto reference hook state- Remove
error/setErrorlocal state (now from analysis hook)
Step 5: Integrate useDiagrams
Remove from App.tsx:
useStatefor: activeTab, nDiagram, n1Diagram, n1Loading, selectedActionId, actionDiagram, actionDiagramLoading, actionViewMode, originalViewBox, inspectQuery, nominalVoltageMap, uniqueVoltages, voltageRange, vlOverlayuseReffor: nSvgContainerRef, n1SvgContainerRef, actionSvgContainerRef, committedBranchRef, restoringSessionRef, lastZoomState, actionSyncSourceRef, activeTabRef, prevTabRefusePanZoomcalls (nPZ, n1PZ, actionPZ)useMemofor metadata indices (nMetaIndex, n1MetaIndex, actionMetaIndex)useMemofor inspectableItemsfetchBaseDiagram,handleActionSelect,handleViewModeChange,handleManualZoomIn/Out,handleManualResethandleVlDoubleClick,handleOverlaySldTabChange,handleOverlayClose,handleAssetClickfetchSldVariant,zoomToElement- Tab synchronization
useLayoutEffect - Action sync
useEffect - id-map cache invalidation effects
- Voltage range filter logic + effects
Add:
const diagrams = useDiagrams(branches, voltageLevels);and destructure- Effect: sync
diagrams.selectedBranchForSld.current = selectedBranch - Wrapper for
handleActionSelectthat passesresult, selectedBranch, voltageLevels.length, setResult, setError - Wrapper for
handleAssetClickthat passesdiagrams.selectedActionId, wrappedActionSelect
Keep in App.tsx (NOT in useDiagrams):
- N-1 diagram fetch effect (uses selectedBranch, branches, hasAnalysisState, clearContingencyState)
- Highlights effect (applyHighlightsForTab + its useEffect)
- Auto-zoom effect (inspectQuery/selectedBranch -> zoomToElement)
- Overloads sync effect (n1Diagram -> setSelectedOverloads)
Step 6: Integrate useSession
Remove from App.tsx:
useStatefor: showReloadModal, sessionList, sessionListLoading, sessionRestoringhandleSaveResults,handleOpenReloadModal,handleRestoreSessioncallbacks
Add:
const session = useSession();- Wrapper for
handleSaveResultsthat buildsSaveParamsfrom settings/analysis/actions/diagrams state - Wrapper for
handleOpenReloadModalthat passesoutputFolderPath, setError - Wrapper for
handleRestoreSessionthat buildsRestoreContextfrom all hooks
Step 7: Clean up imports
Remove unused imports from App.tsx:
usePanZoom(now used inside useDiagrams)buildMetadataIndex,getIdMap,invalidateIdMapCache(now in useDiagrams)buildSessionResult(now in useSession)- Unused type imports:
DiagramData,ViewBox,MetadataIndex,SettingsBackup,VlOverlay,SldTab,FlowDelta,AssetDelta,SessionResult,CombinedAction
Keep:
applyOverloadedHighlights,applyDeltaVisuals,applyActionTargetHighlights,applyContingencyHighlight(used in highlights effect)processSvgAsync(used in N-1 fetch effect)api(used in handleLoadConfig, handleApplySettings, N-1 fetch)- Type imports:
ActionDetail,TabId(used in highlights effect)
Step 8: Verify & commit
- Run
npx tsc --noEmitto verify compilation - Run
npm run lintto check ESLint - Fix any issues
- Commit and push
Cross-Hook Wiring (App.tsx orchestration)
These wrapper callbacks in App.tsx connect the hooks together:
// Wrap diagrams.handleActionSelect with current state
const wrappedActionSelect = (actionId: string | null) =>
diagrams.handleActionSelect(actionId, result, selectedBranch, voltageLevels.length, setResult, setError);
// Wrap actionsHook.handleActionFavorite with setResult
const wrappedActionFavorite = (actionId: string) =>
actionsHook.handleActionFavorite(actionId, setResult);
// Wrap actionsHook.handleManualActionAdded with setResult + action select
const wrappedManualActionAdded = (actionId: string, detail: ActionDetail, linesOverloaded: string[]) =>
actionsHook.handleManualActionAdded(actionId, detail, linesOverloaded, setResult, wrappedActionSelect);
// Wrap analysis.handleRunAnalysis with current branch + clearContingencyState
const wrappedRunAnalysis = () =>
analysis.handleRunAnalysis(selectedBranch, clearContingencyState, setSuggestedByRecommenderIds);
// Wrap analysis.handleDisplayPrioritizedActions with selectedActionIds
const wrappedDisplayPrioritized = () =>
analysis.handleDisplayPrioritizedActions(selectedActionIds);
// Wrap diagrams.handleAssetClick with current selectedActionId + action select
const wrappedAssetClick = (actionId: string, assetName: string, tab: 'action' | 'n' | 'n-1' = 'action') =>
diagrams.handleAssetClick(actionId, assetName, tab, diagrams.selectedActionId, wrappedActionSelect);
App-Level Effects (remain in App.tsx)
- N-1 fetch effect - watches
selectedBranch, handles confirmation dialog, callsapi.getN1Diagram - Overloads sync - watches
n1Diagram?.lines_overloaded->setSelectedOverloads - PDF tab switch - watches
result?.pdf_url+analysisLoading->setActiveTab('overflow') - Highlights effect -
applyHighlightsForTabfor n-1/action tabs - Auto-zoom effect - watches
inspectQuery/selectedBranch->zoomToElement - SLD branch sync - watches
selectedBranch->diagrams.selectedBranchForSld.current
App-Level State (remains in App.tsx)
branches,setBranches- string[]voltageLevels,setVoltageLevels- string[]selectedBranch,setSelectedBranch- stringconfigLoading,setConfigLoading- booleanconfirmDialog,setConfirmDialog- confirmation dialog state
JSX Changes
The JSX stays structurally identical. Variable references change:
networkPath-> stillnetworkPath(destructured from settings)handleActionSelect->wrappedActionSelecthandleActionFavorite->wrappedActionFavoritehandleManualActionAdded->wrappedManualActionAddedhandleRunAnalysis->wrappedRunAnalysishandleDisplayPrioritizedActions->wrappedDisplayPrioritizedhandleAssetClick->wrappedAssetClickhandleSaveResults->wrappedSaveResultshandleOpenReloadModal->wrappedOpenReloadModalhandleRestoreSession->wrappedRestoreSessionsessionRestoring->session.sessionRestoringshowReloadModal->session.showReloadModal- etc.