fix: reduce markdown reprocessing to prevent flicker

- Use stable string keys instead of Map objects as dependencies
- Only clear rendered HTML when markdown content actually changes
- Use refs to access latest Map values without triggering re-renders
- Prevents excessive markdown reprocessing on every profile update
- Should significantly reduce screen flickering during profile resolution
This commit is contained in:
Gigi
2025-11-02 22:42:03 +01:00
parent 27dde5afa2
commit 7ec87b66d8

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect, useRef, useMemo } from 'react'
import { RelayPool } from 'applesauce-relay' import { RelayPool } from 'applesauce-relay'
import { extractNaddrUris, replaceNostrUrisInMarkdownWithProfileLabels } from '../utils/nostrUriResolver' import { extractNaddrUris, replaceNostrUrisInMarkdownWithProfileLabels } from '../utils/nostrUriResolver'
import { fetchArticleTitles } from '../services/articleTitleResolver' import { fetchArticleTitles } from '../services/articleTitleResolver'
@@ -23,6 +23,35 @@ export const useMarkdownToHTML = (
// Resolve profile labels progressively as profiles load // Resolve profile labels progressively as profiles load
const { labels: profileLabels, loading: profileLoading } = useProfileLabels(markdown || '', relayPool) const { labels: profileLabels, loading: profileLoading } = useProfileLabels(markdown || '', relayPool)
// Create stable dependencies based on Map contents, not Map objects
// This prevents unnecessary reprocessing when Maps are recreated with same content
const profileLabelsKey = useMemo(() => {
return Array.from(profileLabels.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${v}`).join('|')
}, [profileLabels])
const profileLoadingKey = useMemo(() => {
return Array.from(profileLoading.entries())
.filter(([, loading]) => loading)
.sort(([a], [b]) => a.localeCompare(b))
.map(([k]) => k)
.join('|')
}, [profileLoading])
const articleTitlesKey = useMemo(() => {
return Array.from(articleTitles.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${v}`).join('|')
}, [articleTitles])
// Keep refs to latest Maps for processing without causing re-renders
const profileLabelsRef = useRef(profileLabels)
const profileLoadingRef = useRef(profileLoading)
const articleTitlesRef = useRef(articleTitles)
useEffect(() => {
profileLabelsRef.current = profileLabels
profileLoadingRef.current = profileLoading
articleTitlesRef.current = articleTitles
}, [profileLabels, profileLoading, articleTitles])
// Fetch article titles // Fetch article titles
useEffect(() => { useEffect(() => {
@@ -54,15 +83,27 @@ export const useMarkdownToHTML = (
return () => { isCancelled = true } return () => { isCancelled = true }
}, [markdown, relayPool]) }, [markdown, relayPool])
// Process markdown with progressive profile labels and article titles // Track previous markdown and processed state to detect actual content changes
const previousMarkdownRef = useRef<string | undefined>(markdown)
const processedMarkdownRef = useRef<string>(processedMarkdown)
useEffect(() => { useEffect(() => {
console.log(`[profile-loading-debug][markdown-to-html] Processing markdown, profileLabels=${profileLabels.size}, profileLoading=${profileLoading.size}, articleTitles=${articleTitles.size}`) processedMarkdownRef.current = processedMarkdown
console.log(`[profile-loading-debug][markdown-to-html] Clearing rendered HTML and processed markdown`) }, [processedMarkdown])
// Always clear previous render immediately to avoid showing stale content while processing
setRenderedHtml('') // Process markdown with progressive profile labels and article titles
setProcessedMarkdown('') // Use stable string keys instead of Map objects to prevent excessive reprocessing
useEffect(() => {
const labelsSize = profileLabelsRef.current.size
const loadingSize = profileLoadingRef.current.size
const titlesSize = articleTitlesRef.current.size
console.log(`[profile-loading-debug][markdown-to-html] Processing markdown, profileLabels=${labelsSize}, profileLoading=${loadingSize}, articleTitles=${titlesSize}`)
if (!markdown) { if (!markdown) {
setRenderedHtml('')
setProcessedMarkdown('')
previousMarkdownRef.current = markdown
processedMarkdownRef.current = ''
return return
} }
@@ -71,21 +112,27 @@ export const useMarkdownToHTML = (
const processMarkdown = () => { const processMarkdown = () => {
try { try {
// Replace nostr URIs with profile labels (progressive) and article titles // Replace nostr URIs with profile labels (progressive) and article titles
// Use refs to get latest values without causing dependency changes
const processed = replaceNostrUrisInMarkdownWithProfileLabels( const processed = replaceNostrUrisInMarkdownWithProfileLabels(
markdown, markdown,
profileLabels, profileLabelsRef.current,
articleTitles, articleTitlesRef.current,
profileLoading profileLoadingRef.current
) )
if (isCancelled) return if (isCancelled) return
console.log(`[profile-loading-debug][markdown-to-html] Processed markdown, loading states:`, Array.from(profileLoading.entries()).filter(([, l]) => l).map(([e]) => e.slice(0, 16) + '...')) const loadingStates = Array.from(profileLoadingRef.current.entries())
.filter(([, l]) => l)
.map(([e]) => e.slice(0, 16) + '...')
console.log(`[profile-loading-debug][markdown-to-html] Processed markdown, loading states:`, loadingStates)
setProcessedMarkdown(processed) setProcessedMarkdown(processed)
processedMarkdownRef.current = processed
} catch (error) { } catch (error) {
console.error(`[markdown-to-html] Error processing markdown:`, error) console.error(`[markdown-to-html] Error processing markdown:`, error)
if (!isCancelled) { if (!isCancelled) {
setProcessedMarkdown(markdown) // Fallback to original setProcessedMarkdown(markdown) // Fallback to original
processedMarkdownRef.current = markdown
} }
} }
@@ -101,12 +148,24 @@ export const useMarkdownToHTML = (
return () => cancelAnimationFrame(rafId) return () => cancelAnimationFrame(rafId)
} }
// Only clear previous content if this is the first processing or markdown changed
// For profile updates, just reprocess without clearing to avoid flicker
const isMarkdownChange = previousMarkdownRef.current !== markdown
previousMarkdownRef.current = markdown
if (isMarkdownChange || !processedMarkdownRef.current) {
console.log(`[profile-loading-debug][markdown-to-html] Clearing rendered HTML and processed markdown (markdown changed: ${isMarkdownChange})`)
setRenderedHtml('')
setProcessedMarkdown('')
processedMarkdownRef.current = ''
}
processMarkdown() processMarkdown()
return () => { return () => {
isCancelled = true isCancelled = true
} }
}, [markdown, profileLabels, profileLoading, articleTitles]) }, [markdown, profileLabelsKey, profileLoadingKey, articleTitlesKey])
return { renderedHtml, previewRef, processedMarkdown } return { renderedHtml, previewRef, processedMarkdown }
} }