Eleven suppressions across the five providers. The recurring shape here was "an effect that undoes something when you navigate away", and every one of them ran a commit LATE — the new page painted once while the old module's state was still live. Adjusted during render instead (React discards that pass rather than committing it, so there is no in-between frame): - SavedViewsProvider reset the sticky view and closed its modal from an effect on `source`, so the first Plex render carried the feed's sticky id — long enough for a stale view chip. It also followed the active view into stickyId from a second effect; that one converges by construction, since preferSticky returns the sticky match when there is one. - NavigationProvider ended a live YouTube search from an effect on `page`. Keyed on the page CHANGE rather than derived from it, because "ended" has to be permanent — deriving would have quietly brought the search back on return to the feed. - ChannelsProvider's manager focus is now simply DERIVED from the page: it means nothing off the Channels page, and focusChannel sets it and navigates in one handler. Honest deps by freezing what is genuinely mount-time: - FeedFiltersProvider persisted a share link's filters with an empty dep list; listing `filters` honestly would have re-persisted on every edit and spun. The mount value is frozen into its own state, so all three deps are stable and the effect says what it does. - ChannelsProvider's URL-seed layout effect and NavigationProvider's initial history stamp get the same treatment. The three slice setters they call were already stable useCallbacks (useFilterState / useSortState / useAccountPersistedState all return one). - SavedViewsProvider reads `cfg` through a ref: it re-memoizes on every filter edit, and the per-source seed guard already enforces once. That also made the set-state finding go away — with the render value gone, the checker can see the seed for what it is. WizardProvider keeps its one escape hatch, now inline with the invariant: auto-opening is a one-way decision taken from server data as it arrives, and deriving it would reopen a wizard the user had just closed. Suppressions 89 -> 78; exhaustive-deps 27 -> 23.
168 lines
3.4 KiB
JSON
168 lines
3.4 KiB
JSON
{
|
|
"src/components/BootGate.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/ChannelPage.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 2
|
|
}
|
|
},
|
|
"src/components/ChannelTags.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 2
|
|
}
|
|
},
|
|
"src/components/ConfigPanel.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/DateFilterSelect.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/DebugRecorder.dom.test.tsx": {
|
|
"react-hooks/globals": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/DebugRecorder.tsx": {
|
|
"react-hooks/purity": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/DiagnosticsSettings.tsx": {
|
|
"react-hooks/purity": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/DownloadCenter.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/Feed.tsx": {
|
|
"react-hooks/immutability": {
|
|
"count": 1
|
|
},
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/FeedFiltersProvider.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/GlassTuner.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
},
|
|
"react-hooks/use-memo": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/Home.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 2
|
|
}
|
|
},
|
|
"src/components/IssueComposer.tsx": {
|
|
"react-hooks/immutability": {
|
|
"count": 2
|
|
}
|
|
},
|
|
"src/components/MyLogViewer.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/PersonalViewsModal.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/PlayerModal.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/Playlists.tsx": {
|
|
"react-hooks/immutability": {
|
|
"count": 2
|
|
},
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/PlexBrowse.tsx": {
|
|
"react-hooks/immutability": {
|
|
"count": 3
|
|
},
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/PlexInfo.tsx": {
|
|
"react-hooks/static-components": {
|
|
"count": 3
|
|
}
|
|
},
|
|
"src/components/PlexPlayer.tsx": {
|
|
"react-hooks/immutability": {
|
|
"count": 1
|
|
},
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/PlexSearchBox.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/Scheduler.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 2
|
|
}
|
|
},
|
|
"src/components/SetupWizard.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/TagFilterChips.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/WatchPage.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/Welcome.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/comfortPin.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/downloadRow.tsx": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
},
|
|
"src/components/usePlexPlaylistEdits.ts": {
|
|
"react-hooks/set-state-in-effect": {
|
|
"count": 1
|
|
}
|
|
}
|
|
}
|