refactor: simplify highlights state management - remove prop drilling

Remove unnecessary prop drilling of myHighlights/myHighlightsLoading.
Components now subscribe directly to highlightsController (DRY principle).

Changes:
- Explore: Subscribe to controller directly, no props needed
- Me: Subscribe to controller directly, no props needed
- Bookmarks: Remove myHighlights props (no longer passes through)
- App: Remove highlights state, controller manages it internally

Benefits:
-  Simpler code (no prop drilling through 3 layers)
-  More DRY (single source of truth in controller)
-  Consistent with applesauce patterns (like useActiveAccount)
-  Less boilerplate (removed ~30 lines of prop passing)
-  Controller encapsulates all state management

Pattern: Components import and subscribe to controller directly,
just like they use Hooks.useActiveAccount() or other applesauce hooks.
This commit is contained in:
Gigi
2025-10-18 21:39:33 +02:00
parent 0a382e77b9
commit c2e882ec31
4 changed files with 38 additions and 70 deletions

View File

@@ -20,7 +20,6 @@ import { RELAYS } from './config/relays'
import { SkeletonThemeProvider } from './components/Skeletons'
import { DebugBus } from './utils/debugBus'
import { Bookmark } from './types/bookmarks'
import { Highlight } from './types/highlights'
import { bookmarkController } from './services/bookmarkController'
import { contactsController } from './services/contactsController'
import { highlightsController } from './services/highlightsController'
@@ -49,10 +48,6 @@ function AppRoutes({
const [contacts, setContacts] = useState<Set<string>>(new Set())
const [contactsLoading, setContactsLoading] = useState(false)
// Centralized highlights state (fed by controller)
const [highlights, setHighlights] = useState<Highlight[]>([])
const [highlightsLoading, setHighlightsLoading] = useState(false)
// Subscribe to bookmark controller
useEffect(() => {
console.log('[bookmark] 🎧 Subscribing to bookmark controller')
@@ -91,24 +86,6 @@ function AppRoutes({
}
}, [])
// Subscribe to highlights controller
useEffect(() => {
console.log('[highlights] 🎧 Subscribing to highlights controller')
const unsubHighlights = highlightsController.onHighlights((highlights) => {
console.log('[highlights] 📥 Received highlights:', highlights.length)
setHighlights(highlights)
})
const unsubLoading = highlightsController.onLoading((loading) => {
console.log('[highlights] 📥 Loading state:', loading)
setHighlightsLoading(loading)
})
return () => {
console.log('[highlights] 🔇 Unsubscribing from highlights controller')
unsubHighlights()
unsubLoading()
}
}, [])
// Auto-load bookmarks, contacts, and highlights when account is ready (on login or page mount)
useEffect(() => {
@@ -127,13 +104,13 @@ function AppRoutes({
contactsController.start({ relayPool, pubkey })
}
// Load highlights
if (pubkey && eventStore && highlights.length === 0 && !highlightsLoading) {
// Load highlights (controller manages its own state)
if (pubkey && eventStore && !highlightsController.isLoadedFor(pubkey)) {
console.log('[highlights] 🚀 Auto-loading highlights on mount/login')
highlightsController.start({ relayPool, eventStore, pubkey })
}
}
}, [activeAccount, relayPool, eventStore, bookmarks.length, bookmarksLoading, contacts.size, contactsLoading, highlights.length, highlightsLoading, accountManager])
}, [activeAccount, relayPool, eventStore, bookmarks.length, bookmarksLoading, contacts.size, contactsLoading, accountManager])
// Manual refresh (for sidebar button)
const handleRefreshBookmarks = useCallback(async () => {
@@ -165,8 +142,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -179,8 +154,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -193,8 +166,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -207,8 +178,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -221,8 +190,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -235,8 +202,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -253,8 +218,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -267,8 +230,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -281,8 +242,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -295,8 +254,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -309,8 +266,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -323,8 +278,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -337,8 +290,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>
@@ -351,8 +302,6 @@ function AppRoutes({
bookmarks={bookmarks}
bookmarksLoading={bookmarksLoading}
onRefreshBookmarks={handleRefreshBookmarks}
myHighlights={highlights}
myHighlightsLoading={highlightsLoading}
/>
}
/>

View File

@@ -14,7 +14,6 @@ import { useBookmarksUI } from '../hooks/useBookmarksUI'
import { useRelayStatus } from '../hooks/useRelayStatus'
import { useOfflineSync } from '../hooks/useOfflineSync'
import { Bookmark } from '../types/bookmarks'
import { Highlight } from '../types/highlights'
import ThreePaneLayout from './ThreePaneLayout'
import Explore from './Explore'
import Me from './Me'
@@ -29,8 +28,6 @@ interface BookmarksProps {
bookmarks: Bookmark[]
bookmarksLoading: boolean
onRefreshBookmarks: () => Promise<void>
myHighlights: Highlight[]
myHighlightsLoading: boolean
}
const Bookmarks: React.FC<BookmarksProps> = ({
@@ -38,9 +35,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({
onLogout,
bookmarks,
bookmarksLoading,
onRefreshBookmarks,
myHighlights,
myHighlightsLoading
onRefreshBookmarks
}) => {
const { naddr, npub } = useParams<{ naddr?: string; npub?: string }>()
const location = useLocation()
@@ -327,10 +322,10 @@ const Bookmarks: React.FC<BookmarksProps> = ({
onCreateHighlight={handleCreateHighlight}
hasActiveAccount={!!(activeAccount && relayPool)}
explore={showExplore ? (
relayPool ? <Explore relayPool={relayPool} eventStore={eventStore} settings={settings} activeTab={exploreTab} myHighlights={myHighlights} myHighlightsLoading={myHighlightsLoading} /> : null
relayPool ? <Explore relayPool={relayPool} eventStore={eventStore} settings={settings} activeTab={exploreTab} /> : null
) : undefined}
me={showMe ? (
relayPool ? <Me relayPool={relayPool} activeTab={meTab} bookmarks={bookmarks} bookmarksLoading={bookmarksLoading} myHighlights={myHighlights} myHighlightsLoading={myHighlightsLoading} /> : null
relayPool ? <Me relayPool={relayPool} activeTab={meTab} bookmarks={bookmarks} bookmarksLoading={bookmarksLoading} /> : null
) : undefined}
profile={showProfile && profilePubkey ? (
relayPool ? <Me relayPool={relayPool} activeTab={profileTab} pubkey={profilePubkey} bookmarks={bookmarks} bookmarksLoading={bookmarksLoading} /> : null

View File

@@ -13,6 +13,7 @@ import { fetchBlogPostsFromAuthors, BlogPostPreview } from '../services/exploreS
import { fetchHighlightsFromAuthors } from '../services/highlightService'
import { fetchProfiles } from '../services/profileService'
import { fetchNostrverseBlogPosts, fetchNostrverseHighlights } from '../services/nostrverseService'
import { highlightsController } from '../services/highlightsController'
import { Highlight } from '../types/highlights'
import { UserSettings } from '../services/settingsService'
import BlogPostCard from './BlogPostCard'
@@ -28,13 +29,11 @@ interface ExploreProps {
eventStore: IEventStore
settings?: UserSettings
activeTab?: TabType
myHighlights?: Highlight[] // From highlightsController in App.tsx
myHighlightsLoading?: boolean // Loading state from highlightsController
}
type TabType = 'writings' | 'highlights'
const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, activeTab: propActiveTab, myHighlights = [], myHighlightsLoading = false }) => {
const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, activeTab: propActiveTab }) => {
const activeAccount = Hooks.useActiveAccount()
const navigate = useNavigate()
const [activeTab, setActiveTab] = useState<TabType>(propActiveTab || 'highlights')
@@ -44,6 +43,10 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
const [loading, setLoading] = useState(true)
const [refreshTrigger, setRefreshTrigger] = useState(0)
// Get myHighlights directly from controller
const [myHighlights, setMyHighlights] = useState<Highlight[]>([])
const [myHighlightsLoading, setMyHighlightsLoading] = useState(false)
// Visibility filters (defaults from settings, or friends only)
const [visibility, setVisibility] = useState<HighlightVisibility>({
nostrverse: settings?.defaultHighlightVisibilityNostrverse ?? false,
@@ -51,6 +54,16 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
mine: settings?.defaultHighlightVisibilityMine ?? false
})
// Subscribe to highlights controller
useEffect(() => {
const unsubHighlights = highlightsController.onHighlights(setMyHighlights)
const unsubLoading = highlightsController.onLoading(setMyHighlightsLoading)
return () => {
unsubHighlights()
unsubLoading()
}
}, [])
// Update local state when prop changes
useEffect(() => {
if (propActiveTab) {

View File

@@ -9,6 +9,7 @@ import { useNavigate, useParams } from 'react-router-dom'
import { Highlight } from '../types/highlights'
import { HighlightItem } from './HighlightItem'
import { fetchHighlights } from '../services/highlightService'
import { highlightsController } from '../services/highlightsController'
import { fetchAllReads, ReadItem } from '../services/readsService'
import { fetchLinks } from '../services/linksService'
import { BlogPostPreview, fetchBlogPostsFromAuthors } from '../services/exploreService'
@@ -38,8 +39,6 @@ interface MeProps {
pubkey?: string // Optional pubkey for viewing other users' profiles
bookmarks: Bookmark[] // From centralized App.tsx state
bookmarksLoading?: boolean // From centralized App.tsx state (reserved for future use)
myHighlights?: Highlight[] // From highlightsController (for own profile)
myHighlightsLoading?: boolean // Loading state from highlightsController
}
type TabType = 'highlights' | 'reading-list' | 'reads' | 'links' | 'writings'
@@ -51,9 +50,7 @@ const Me: React.FC<MeProps> = ({
relayPool,
activeTab: propActiveTab,
pubkey: propPubkey,
bookmarks,
myHighlights = [],
myHighlightsLoading = false
bookmarks
}) => {
const activeAccount = Hooks.useActiveAccount()
const navigate = useNavigate()
@@ -71,6 +68,10 @@ const Me: React.FC<MeProps> = ({
const [writings, setWritings] = useState<BlogPostPreview[]>([])
const [loading, setLoading] = useState(true)
const [loadedTabs, setLoadedTabs] = useState<Set<TabType>>(new Set())
// Get myHighlights directly from controller
const [myHighlights, setMyHighlights] = useState<Highlight[]>([])
const [myHighlightsLoading, setMyHighlightsLoading] = useState(false)
const [viewMode, setViewMode] = useState<ViewMode>('cards')
const [refreshTrigger, setRefreshTrigger] = useState(0)
const [bookmarkFilter, setBookmarkFilter] = useState<BookmarkFilterType>('all')
@@ -91,6 +92,16 @@ const Me: React.FC<MeProps> = ({
: 'all'
const [readingProgressFilter, setReadingProgressFilter] = useState<ReadingProgressFilterType>(initialFilter)
// Subscribe to highlights controller
useEffect(() => {
const unsubHighlights = highlightsController.onHighlights(setMyHighlights)
const unsubLoading = highlightsController.onLoading(setMyHighlightsLoading)
return () => {
unsubHighlights()
unsubLoading()
}
}, [])
// Update local state when prop changes
useEffect(() => {
if (propActiveTab) {