fix(rules): re-scroll directory hash when data refreshes

Remove the one-shot scroll guard so /rules#code anchors scroll again when directory data updates after the initial render.
This commit is contained in:
Tommaso Casaburi
2026-06-15 18:39:49 +07:00
parent f3565f0dda
commit 6006a658fb
3 changed files with 33 additions and 7 deletions
@@ -95,8 +95,18 @@ vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
}));
vi.mock('@bitsocial/bitsocial-react-hooks/dist/stores/accounts/index.js', () => ({
default: (selector: (state: { accountsEditsSummaries: Record<string, Record<string, unknown>>; activeAccountId: string }) => unknown) =>
selector({ accountsEditsSummaries: { account: {} }, activeAccountId: 'account' }),
default: (
selector: (state: {
accounts: Record<string, typeof testState.account>;
accountsEditsSummaries: Record<string, Record<string, unknown>>;
activeAccountId: string;
}) => unknown,
) =>
selector({
accounts: { account: testState.account },
accountsEditsSummaries: { account: {} },
activeAccountId: 'account',
}),
}));
vi.mock('@floating-ui/react', () => ({
@@ -150,6 +160,8 @@ vi.mock('../../../stores/use-challenges-store', () => {
});
vi.mock('../../../hooks/use-account-community-addresses', () => ({
areStringArraysEqual: (previous: readonly string[] | undefined, next: readonly string[] | undefined) =>
previous === next || (!!previous && !!next && previous.length === next.length && previous.every((value, index) => value === next[index])),
useAccountCommunityAddresses: () => testState.accountCommunityAddresses,
}));
+17
View File
@@ -156,6 +156,23 @@ describe('Rules', () => {
expect(scrollIntoViewMock).toHaveBeenCalled();
});
it('re-scrolls an active directory hash when directory data refreshes', async () => {
await renderRules('/rules#b');
expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
testState.directories = [{ address: 'animals-posting.eth', title: '/an/ - Animals & Nature', directoryCode: 'an' }, ...testState.directories];
testState.directoryDefaults = {
directories: {
...testState.directoryDefaults.directories,
an: { directoryCode: 'an', title: '/an/ - Animals & Nature', rules: ['Keep animal posts on topic.'] },
},
};
await renderRules('/rules#b');
expect(scrollIntoViewMock).toHaveBeenCalledTimes(2);
});
it('ignores address hashes instead of resolving them to directory rules or P2P rules', async () => {
testState.communities = {
'anime-posting.eth': {
+2 -5
View File
@@ -1,4 +1,4 @@
import { Fragment, useEffect, useRef, useState, FormEvent } from 'react';
import { Fragment, useEffect, useState, FormEvent } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useCommunity } from '@bitsocial/bitsocial-react-hooks';
import { Footer, HomeLogo } from '../home/home';
@@ -240,7 +240,6 @@ const Rules = () => {
const directories = useDirectories();
const directoryDefaults = useDirectoryDefaults();
const [loadedAddress, setLoadedAddress] = useState('');
const scrolledForRef = useRef<string | null>(null);
const hashCode = getRulesHashCode(hash);
// Order directories alphabetically by directory code (e.g. /3/, /a/, /aco/...), like 4chan, not by title.
@@ -257,7 +256,6 @@ const Rules = () => {
useEffect(() => {
setLoadedAddress('');
scrolledForRef.current = null;
if (!hashCode) {
window.scrollTo(0, 0);
}
@@ -265,13 +263,12 @@ const Rules = () => {
// Deep-link: /rules#code insta-scrolls to a known directory's rules once the matching section is rendered.
useEffect(() => {
if (!hashCode || scrolledForRef.current === hashCode || !isDirectoryRoute(hashCode, directories)) {
if (!hashCode || !isDirectoryRoute(hashCode, directories)) {
return;
}
const element = document.getElementById(hashCode);
if (element) {
element.scrollIntoView();
scrolledForRef.current = hashCode;
}
}, [hashCode, directories]);