mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-21 06:09:56 -05:00
fix: model category reload flickering
This commit is contained in:
1 parent
91ce40f9ff
commit
cdd2525085
2 files changed
+31
-5
No files matched your search
@@ -68,7 +68,15 @@ function AppShell({
|
||||
onUserUpdated,
|
||||
}: AppShellProps) {
|
||||
const navigate = useNavigate();
|
||||
const [categoryId, setCategoryId] = React.useState<string | null>(null);
|
||||
// Lazily seeded from the current URL (not just null) so a direct load/refresh of
|
||||
// /models?category=<id> already has the right categoryId on its very first render, instead of
|
||||
// starting unfiltered and correcting a moment later once ModelsPage's own URL-sync effect
|
||||
// catches up -- that gap was a real, visible flash of "All" before the filtered view. Only
|
||||
// matters at mount: normal in-app navigation keeps this in sync via onSelectCategory as usual.
|
||||
const [categoryId, setCategoryId] = React.useState<string | null>(() => {
|
||||
if (typeof window === "undefined" || !window.location.pathname.startsWith("/models")) return null;
|
||||
return new URLSearchParams(window.location.search).get("category");
|
||||
});
|
||||
const [nonce, setNonce] = React.useState(0);
|
||||
const [categoryVersion, setCategoryVersion] = React.useState(0);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import Box from "@mui/material/Box";
|
||||
@@ -57,20 +57,38 @@ export default function ModelsPage({ categoryId, onSelectCategory, categoriesVer
|
||||
|
||||
// Keeps the selected category mirrored into ?category=<id> so the URL is copy-able/bookmarkable
|
||||
// and a reload lands back on the same filtered view, while categoryId itself stays lifted to
|
||||
// AppShell (it also needs to survive navigating away to a model and back). Two effects, one per
|
||||
// direction, each a no-op once the two already agree, so they can't loop against each other.
|
||||
// AppShell (it also needs to survive navigating away to a model and back).
|
||||
//
|
||||
// Two effects, one per direction -- but whenever categoryId and the URL start a render already
|
||||
// disagreeing (a cold load of ?category=X while categoryId still defaults to null, or a browser
|
||||
// back/forward that changes the URL out from under an unrelated categoryId), BOTH used to fire
|
||||
// in that same commit, each reading the OTHER's still-stale value: the URL->state effect would
|
||||
// schedule categoryId to catch up to the URL, while the state->URL effect -- seeing that same
|
||||
// stale categoryId one commit longer -- would shove the URL back to match it, undoing the first
|
||||
// effect's work. That handoff repeated every render, each one node lagging the other by exactly
|
||||
// one step, which is the flicker. `syncingFromUrlRef` marks a categoryId change as having
|
||||
// originated from the URL (not a user click), so the state->URL effect knows to skip pushing it
|
||||
// right back.
|
||||
const categoryParam = searchParams.get("category");
|
||||
const syncingFromUrlRef = useRef(false);
|
||||
|
||||
// URL -> state: adopts ?category on first load and on any navigation that changes it externally
|
||||
// (browser back/forward, a pasted link).
|
||||
useEffect(() => {
|
||||
if (categoryParam !== categoryId) onSelectCategory(categoryParam);
|
||||
if (categoryParam !== categoryId) {
|
||||
syncingFromUrlRef.current = true;
|
||||
onSelectCategory(categoryParam);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [categoryParam]);
|
||||
|
||||
// state -> URL: covers every other way categoryId can change -- clicking a category, or it being
|
||||
// cleared out from under the user (e.g. deleteCategory below clearing the active selection).
|
||||
useEffect(() => {
|
||||
if (syncingFromUrlRef.current) {
|
||||
syncingFromUrlRef.current = false;
|
||||
return;
|
||||
}
|
||||
if ((searchParams.get("category") || null) === categoryId) return;
|
||||
setSearchParams(prev => {
|
||||
const next = new URLSearchParams(prev);
|
||||
|
||||
Reference in new issue
Block a user