mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
revert refactoring for rpc state rerenders, fix home stats
This commit is contained in:
@@ -69,34 +69,3 @@ export const useSubplebbitField = <T>(subplebbitAddress: string | undefined, sel
|
|||||||
|
|
||||||
return field;
|
return field;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get multiple subplebbits with stable references.
|
|
||||||
* Only re-renders when actual content changes (updatedAt, posts pages), not transient state.
|
|
||||||
*
|
|
||||||
* @param subplebbitAddresses - Array of subplebbit addresses
|
|
||||||
* @returns Array of subplebbit objects
|
|
||||||
*/
|
|
||||||
export const useStableSubplebbits = (subplebbitAddresses: string[]) => {
|
|
||||||
const subplebbits = useSubplebbitsStore(
|
|
||||||
(state) => subplebbitAddresses.map((address) => state.subplebbits[address]),
|
|
||||||
// Custom equality: only re-render if stable content fields change
|
|
||||||
(prev, next) => {
|
|
||||||
if (prev.length !== next.length) return false;
|
|
||||||
return prev.every((p, i) => {
|
|
||||||
const n = next[i];
|
|
||||||
if (p === n) return true;
|
|
||||||
if (!p || !n) return p === n;
|
|
||||||
// Compare stable content fields only - ignore updatingState, state, clients, etc.
|
|
||||||
return (
|
|
||||||
p.address === n.address &&
|
|
||||||
p.updatedAt === n.updatedAt &&
|
|
||||||
// For PopularThreadsBox: compare posts pages reference (changes when new posts loaded)
|
|
||||||
p.posts?.pages?.hot === n.posts?.pages?.hot
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return subplebbits;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,70 +1,13 @@
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useAccount } from '@plebbit/plebbit-react-hooks';
|
import { useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
const pendingFetchCid: { [cid: string]: boolean } = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get stats for multiple subplebbits.
|
|
||||||
* Uses stable store selector to avoid re-renders from updatingState changes.
|
|
||||||
*/
|
|
||||||
const useSubplebbitsStats = (options: any) => {
|
|
||||||
const { subplebbitAddresses, accountName } = options || {};
|
|
||||||
const account = useAccount({ accountName });
|
|
||||||
|
|
||||||
// Use stable selector to only get statsCid for each address
|
|
||||||
// This avoids re-renders when only updatingState changes
|
|
||||||
const statsCids = useSubplebbitsStore(
|
|
||||||
(state) =>
|
|
||||||
(subplebbitAddresses || []).map((address: string) => ({
|
|
||||||
address,
|
|
||||||
statsCid: state.subplebbits[address]?.statsCid,
|
|
||||||
})),
|
|
||||||
// Custom equality: only re-render if statsCid values change
|
|
||||||
(prev, next) => {
|
|
||||||
if (prev.length !== next.length) return false;
|
|
||||||
return prev.every((p: any, i: number) => p.address === next[i].address && p.statsCid === next[i].statsCid);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { setSubplebbitStats, subplebbitsStats } = useSubplebbitsStatsStore();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!subplebbitAddresses || subplebbitAddresses.length === 0 || !account) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
statsCids.forEach(({ address, statsCid }: { address: string; statsCid: string | undefined }) => {
|
|
||||||
if (statsCid && !subplebbitsStats[address] && !pendingFetchCid[statsCid]) {
|
|
||||||
pendingFetchCid[statsCid] = true;
|
|
||||||
account.plebbit
|
|
||||||
.fetchCid(statsCid)
|
|
||||||
.then((fetchedStats: any) => {
|
|
||||||
setSubplebbitStats(address, JSON.parse(fetchedStats));
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
pendingFetchCid[statsCid] = false;
|
|
||||||
console.error('Fetching subplebbit stats failed', { subplebbitAddress: address, error });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [account, statsCids, setSubplebbitStats, subplebbitsStats, subplebbitAddresses]);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
return subplebbitAddresses.reduce((acc: any, address: any) => {
|
|
||||||
acc[address] = subplebbitsStats[address] || { loading: true };
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}, [subplebbitsStats, subplebbitAddresses]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SubplebbitsStatsState = {
|
export type SubplebbitsStatsState = {
|
||||||
subplebbitsStats: { [subplebbitAddress: string]: any };
|
subplebbitsStats: { [subplebbitAddress: string]: any };
|
||||||
setSubplebbitStats: Function;
|
setSubplebbitStats: (subplebbitAddress: string, stats: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useSubplebbitsStatsStore = create<SubplebbitsStatsState>((set) => ({
|
export const useSubplebbitsStatsStore = create<SubplebbitsStatsState>((set) => ({
|
||||||
subplebbitsStats: {},
|
subplebbitsStats: {},
|
||||||
setSubplebbitStats: (subplebbitAddress: string, subplebbitStats: any) =>
|
setSubplebbitStats: (subplebbitAddress: string, subplebbitStats: any) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
@@ -72,4 +15,20 @@ const useSubplebbitsStatsStore = create<SubplebbitsStatsState>((set) => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useSubplebbitsStats;
|
/**
|
||||||
|
* Component that fetches stats for a single subplebbit and stores them.
|
||||||
|
* Render one of these for each subplebbit you want to track stats for.
|
||||||
|
*/
|
||||||
|
export const SubplebbitStatsCollector = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
|
||||||
|
const stats = useSubplebbitStats({ subplebbitAddress });
|
||||||
|
const setSubplebbitStats = useSubplebbitsStatsStore((state) => state.setSubplebbitStats);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Only update store when we have actual stats (not just loading state)
|
||||||
|
if (stats && stats.allPostCount !== undefined) {
|
||||||
|
setSubplebbitStats(subplebbitAddress, stats);
|
||||||
|
}
|
||||||
|
}, [stats, subplebbitAddress, setSubplebbitStats]);
|
||||||
|
|
||||||
|
return null; // This is a data-fetching component, renders nothing
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||||
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Comment, useAccount, useFeed, useBlock, useAccountComments } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAccount, useFeed, useSubplebbit, useBlock, useAccountComments } from '@plebbit/plebbit-react-hooks';
|
||||||
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
|
||||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
|
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
|
||||||
@@ -480,14 +479,8 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
|||||||
}
|
}
|
||||||
}, [reset, setResetFunction, isVisible]);
|
}, [reset, setResetFunction, isVisible]);
|
||||||
|
|
||||||
// Use stable field selectors to avoid re-renders from updatingState changes
|
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||||
const error = useSubplebbitField(subplebbitAddress, (sub) => sub?.error);
|
const { error, shortAddress, state, title } = subplebbit || {};
|
||||||
const shortAddress = useSubplebbitField(subplebbitAddress, (sub) => sub?.shortAddress);
|
|
||||||
const title = useSubplebbitField(subplebbitAddress, (sub) => sub?.title);
|
|
||||||
// Derive state from updatedAt field - if updatedAt exists, state is 'succeeded'
|
|
||||||
const updatedAt = useSubplebbitField(subplebbitAddress, (sub) => sub?.updatedAt);
|
|
||||||
const state = updatedAt ? 'succeeded' : 'fetching-ipns';
|
|
||||||
|
|
||||||
const { blocked, unblock } = useBlock({ address: subplebbitAddress });
|
const { blocked, unblock } = useBlock({ address: subplebbitAddress });
|
||||||
|
|
||||||
const feedLength = feed.length;
|
const feedLength = feed.length;
|
||||||
|
|||||||
+20
-15
@@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useMemo, useRef, FormEvent } from 'react';
|
import { useEffect, useMemo, useRef, FormEvent } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { useSubplebbits } from '@plebbit/plebbit-react-hooks';
|
||||||
import styles from './home.module.css';
|
import styles from './home.module.css';
|
||||||
import { useDefaultSubplebbits, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbits, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||||
import useSubplebbitsStats from '../../hooks/use-subplebbits-stats';
|
import { SubplebbitStatsCollector, useSubplebbitsStatsStore } from '../../hooks/use-subplebbits-stats';
|
||||||
import PopularThreadsBox from './popular-threads-box';
|
import PopularThreadsBox from './popular-threads-box';
|
||||||
import BoardsList from './boards-list';
|
import BoardsList from './boards-list';
|
||||||
import Version from '../../components/version';
|
import Version from '../../components/version';
|
||||||
@@ -74,29 +75,31 @@ const InfoBox = () => {
|
|||||||
|
|
||||||
const Stats = ({ subplebbitAddresses }: { subplebbitAddresses: string[] }) => {
|
const Stats = ({ subplebbitAddresses }: { subplebbitAddresses: string[] }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const stats = useSubplebbitsStats({ subplebbitAddresses });
|
const subplebbitsStats = useSubplebbitsStatsStore((state) => state.subplebbitsStats);
|
||||||
|
|
||||||
const allStatsLoaded = useMemo(() => {
|
const { totalPosts, currentUsers, boardsTracked } = useMemo(() => {
|
||||||
return subplebbitAddresses.every((address) => stats[address]);
|
|
||||||
}, [stats, subplebbitAddresses]);
|
|
||||||
|
|
||||||
const { totalPosts, currentUsers } = useMemo(() => {
|
|
||||||
let totalPosts = 0;
|
let totalPosts = 0;
|
||||||
let currentUsers = 0;
|
let currentUsers = 0;
|
||||||
|
let boardsTracked = 0;
|
||||||
|
|
||||||
if (allStatsLoaded) {
|
subplebbitAddresses.forEach((address) => {
|
||||||
Object.values(stats).forEach((stat: any) => {
|
const stat = subplebbitsStats[address];
|
||||||
|
if (stat) {
|
||||||
totalPosts += stat.allPostCount || 0;
|
totalPosts += stat.allPostCount || 0;
|
||||||
currentUsers += stat.weekActiveUserCount || 0;
|
currentUsers += stat.weekActiveUserCount || 0;
|
||||||
});
|
boardsTracked++;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { totalPosts, currentUsers };
|
return { totalPosts, currentUsers, boardsTracked };
|
||||||
}, [stats, allStatsLoaded]);
|
}, [subplebbitsStats, subplebbitAddresses]);
|
||||||
|
|
||||||
const boardsTracked = Object.values(stats).filter((stat: any) => stat && !stat.loading).length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{/* Render collectors to fetch stats for each subplebbit */}
|
||||||
|
{subplebbitAddresses.map((address) => (
|
||||||
|
<SubplebbitStatsCollector key={address} subplebbitAddress={address} />
|
||||||
|
))}
|
||||||
<div className={styles.box}>
|
<div className={styles.box}>
|
||||||
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
|
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
|
||||||
<h2 className='capitalize'>{t('stats')}</h2>
|
<h2 className='capitalize'>{t('stats')}</h2>
|
||||||
@@ -113,6 +116,7 @@ const Stats = ({ subplebbitAddresses }: { subplebbitAddresses: string[] }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -190,6 +194,7 @@ export const HomeLogo = () => {
|
|||||||
const Home = () => {
|
const Home = () => {
|
||||||
const defaultSubplebbits = useDefaultSubplebbits();
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
const subplebbitAddresses = useDefaultSubplebbitAddresses();
|
const subplebbitAddresses = useDefaultSubplebbitAddresses();
|
||||||
|
const { subplebbits } = useSubplebbits({ subplebbitAddresses });
|
||||||
const { closeDirectoryModal } = useDirectoryModalStore();
|
const { closeDirectoryModal } = useDirectoryModalStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -212,7 +217,7 @@ const Home = () => {
|
|||||||
<SearchBar />
|
<SearchBar />
|
||||||
<InfoBox />
|
<InfoBox />
|
||||||
<BoardsList multisub={defaultSubplebbits} />
|
<BoardsList multisub={defaultSubplebbits} />
|
||||||
<PopularThreadsBox multisub={defaultSubplebbits} subplebbitAddresses={subplebbitAddresses} />
|
<PopularThreadsBox multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
||||||
<Stats subplebbitAddresses={subplebbitAddresses} />
|
<Stats subplebbitAddresses={subplebbitAddresses} />
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { memo, useMemo, useEffect } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Comment, Subplebbit, useAccount } from '@plebbit/plebbit-react-hooks';
|
import { Comment, Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
|
||||||
import styles from '../home.module.css';
|
import styles from '../home.module.css';
|
||||||
import usePopularPosts from '../../../hooks/use-popular-posts';
|
import usePopularPosts from '../../../hooks/use-popular-posts';
|
||||||
import usePopularThreadsOptionsStore from '../../../stores/use-popular-threads-options-store';
|
import usePopularThreadsOptionsStore from '../../../stores/use-popular-threads-options-store';
|
||||||
@@ -13,7 +12,6 @@ import BoxModal from '../box-modal';
|
|||||||
import { MultisubSubplebbit, useDefaultSubplebbits } from '../../../hooks/use-default-subplebbits';
|
import { MultisubSubplebbit, useDefaultSubplebbits } from '../../../hooks/use-default-subplebbits';
|
||||||
import { getBoardPath } from '../../../lib/utils/route-utils';
|
import { getBoardPath } from '../../../lib/utils/route-utils';
|
||||||
import { removeMarkdown } from '../../../lib/utils/post-utils';
|
import { removeMarkdown } from '../../../lib/utils/post-utils';
|
||||||
import { useStableSubplebbits } from '../../../hooks/use-stable-subplebbit';
|
|
||||||
|
|
||||||
interface PopularThreadProps {
|
interface PopularThreadProps {
|
||||||
post: Comment;
|
post: Comment;
|
||||||
@@ -69,25 +67,10 @@ const PopularThreadCard = memo(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Uses stable subplebbits hook to avoid re-renders from updatingState changes
|
const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: MultisubSubplebbit[]; subplebbits: any }) => {
|
||||||
const PopularThreadsBox = ({ multisub, subplebbitAddresses }: { multisub: MultisubSubplebbit[]; subplebbitAddresses: string[] }) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { showWorksafeContentOnly, showNsfwContentOnly } = usePopularThreadsOptionsStore();
|
const { showWorksafeContentOnly, showNsfwContentOnly } = usePopularThreadsOptionsStore();
|
||||||
|
|
||||||
const account = useAccount();
|
|
||||||
const addSubplebbitToStore = useSubplebbitsStore((state) => state.addSubplebbitToStore);
|
|
||||||
|
|
||||||
// Trigger fetching subplebbits (same as useSubplebbits does internally)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!account || !subplebbitAddresses) return;
|
|
||||||
for (const address of subplebbitAddresses) {
|
|
||||||
addSubplebbitToStore(address, account).catch(() => {});
|
|
||||||
}
|
|
||||||
}, [subplebbitAddresses?.toString(), account?.id]);
|
|
||||||
|
|
||||||
// Use stable hook that only re-renders when actual content changes
|
|
||||||
const subplebbits = useStableSubplebbits(subplebbitAddresses);
|
|
||||||
|
|
||||||
const getFilteredSubplebbits = () => {
|
const getFilteredSubplebbits = () => {
|
||||||
if (showWorksafeContentOnly) {
|
if (showWorksafeContentOnly) {
|
||||||
return subplebbits.filter((sub: Subplebbit) => {
|
return subplebbits.filter((sub: Subplebbit) => {
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ const PostPage = () => {
|
|||||||
|
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||||
const { error: subplebbitError, shortAddress, title } = subplebbit || {};
|
const { error: subplebbitError, shortAddress, title } = subplebbit || {};
|
||||||
|
|
||||||
const defaultSubplebbits = useDefaultSubplebbits();
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
|
|
||||||
// if the comment is a reply, return the post comment instead, then the reply will be highlighted in the thread
|
// if the comment is a reply, return the post comment instead, then the reply will be highlighted in the thread
|
||||||
|
|||||||
Reference in New Issue
Block a user