fix: resolve react-doctor errors (hooks order, reduced motion, key prop) (#105)
- Move useCallback hooks before early return in ReferencesDialog to fix conditional hook call violation (Rules of Hooks) - Add useReducedMotion hook and apply to all motion animations for WCAG 2.3.3 accessibility compliance - Replace useEffect state reset with key prop on ItemCatalog for proper React reconciliation on category change - Add global prefers-reduced-motion CSS media query Fixes 4 react-doctor errors, bringing score from 81 to ~85+.
This commit is contained in:
@@ -8,6 +8,7 @@ import { ControlModes } from "./control-modes";
|
||||
import { PhaseSwitcher } from "./phase-switcher";
|
||||
import { StructureTools } from "./structure-tools";
|
||||
import useEditor from "@/store/use-editor";
|
||||
import { useReducedMotion } from "@/hooks/use-reduced-motion";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { ItemCatalog } from "../item-catalog/item-catalog";
|
||||
import { FurnishTools } from "./furnish-tools";
|
||||
@@ -18,6 +19,8 @@ export function ActionMenu({ className }: { className?: string }) {
|
||||
const mode = useEditor((state) => state.mode);
|
||||
const tool = useEditor((state) => state.tool);
|
||||
const catalogCategory = useEditor((state) => state.catalogCategory);
|
||||
const reducedMotion = useReducedMotion();
|
||||
const transition = reducedMotion ? { duration: 0 } : undefined;
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
@@ -57,8 +60,9 @@ export function ActionMenu({ className }: { className?: string }) {
|
||||
paddingBottom: 0,
|
||||
borderBottomWidth: 0,
|
||||
}}
|
||||
transition={transition}
|
||||
>
|
||||
<ItemCatalog category={catalogCategory} />
|
||||
<ItemCatalog key={catalogCategory} category={catalogCategory} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@@ -91,6 +95,7 @@ export function ActionMenu({ className }: { className?: string }) {
|
||||
paddingBottom: 0,
|
||||
borderBottomWidth: 0,
|
||||
}}
|
||||
transition={transition}
|
||||
>
|
||||
<div className="mx-auto w-max">
|
||||
<FurnishTools />
|
||||
@@ -127,6 +132,7 @@ export function ActionMenu({ className }: { className?: string }) {
|
||||
paddingBottom: 0,
|
||||
borderBottomWidth: 0,
|
||||
}}
|
||||
transition={transition}
|
||||
>
|
||||
<div className="w-max">
|
||||
<StructureTools />
|
||||
|
||||
@@ -21,12 +21,6 @@ export function ItemCatalog({ category }: { category: CatalogCategory }) {
|
||||
const [activePlacementTag, setActivePlacementTag] = useState<string | null>(null);
|
||||
const [activeFunctionalTag, setActiveFunctionalTag] = useState<string | null>(null);
|
||||
|
||||
// Reset tag filters when category changes
|
||||
useEffect(() => {
|
||||
setActivePlacementTag(null);
|
||||
setActiveFunctionalTag(null);
|
||||
}, [category]);
|
||||
|
||||
const categoryItems = CATALOG_ITEMS.filter(
|
||||
(item) => item.category === category,
|
||||
);
|
||||
|
||||
@@ -38,15 +38,6 @@ export function ReferencesDialog({ levelId, open, onOpenChange }: ReferencesDial
|
||||
const scanInputRef = useRef<HTMLInputElement>(null)
|
||||
const guideInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const level = nodes[levelId as AnyNodeId] as LevelNode | undefined
|
||||
if (!level) return null
|
||||
|
||||
// Find all scan and guide children of this level
|
||||
const references = Object.values(nodes).filter(
|
||||
(node): node is ScanNode | GuideNode =>
|
||||
(node.type === 'scan' || node.type === 'guide') && node.parentId === levelId,
|
||||
)
|
||||
|
||||
const handleAddScan = useCallback(
|
||||
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
@@ -100,6 +91,15 @@ export function ReferencesDialog({ levelId, open, onOpenChange }: ReferencesDial
|
||||
[deleteNode],
|
||||
)
|
||||
|
||||
const level = nodes[levelId as AnyNodeId] as LevelNode | undefined
|
||||
if (!level) return null
|
||||
|
||||
// Find all scan and guide children of this level
|
||||
const references = Object.values(nodes).filter(
|
||||
(node): node is ScanNode | GuideNode =>
|
||||
(node.type === 'scan' || node.type === 'guide') && node.parentId === levelId,
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
|
||||
Reference in New Issue
Block a user