mirror of
https://github.com/dergigi/boris.git
synced 2025-12-24 10:04:19 +01:00
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:
57
src/App.tsx
57
src/App.tsx
@@ -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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user