From e8a9a42ef47c3394d0e688f10deda7b6e44c7d34 Mon Sep 17 00:00:00 2001 From: Adrian Rumpold Date: Thu, 24 Apr 2025 11:14:05 +0200 Subject: [PATCH] Use zustand for state management --- frontend/package-lock.json | 36 ++++++++++++++++++++++++++++--- frontend/package.json | 3 ++- frontend/src/App.tsx | 26 ++++++++++------------ frontend/src/components/Panel.tsx | 23 ++++++++------------ frontend/src/store/uiStore.ts | 21 ++++++++++++++++++ 5 files changed, 76 insertions(+), 33 deletions(-) create mode 100644 frontend/src/store/uiStore.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f385a44..6c986fd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,8 @@ "@tanstack/react-query-devtools": "^5.74.6", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router-dom": "^7.5.1" + "react-router-dom": "^7.5.1", + "zustand": "^5.0.3" }, "devDependencies": { "@eslint/js": "^9.22.0", @@ -1471,7 +1472,7 @@ "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1963,7 +1964,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -3436,6 +3437,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/frontend/package.json b/frontend/package.json index 311d9d7..9ad1da1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,8 @@ "@tanstack/react-query-devtools": "^5.74.6", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router-dom": "^7.5.1" + "react-router-dom": "^7.5.1", + "zustand": "^5.0.3" }, "devDependencies": { "@eslint/js": "^9.22.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 42fc738..f813cea 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,14 +1,17 @@ -import { useState } from "react"; -import Panel from "./components/Panel"; - import { useQueries } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; -import "./App.css"; -import ArticleSelector from "./components/ArticleSelector"; -import TOC from "./components/TOC"; + import { getArticleIds, getToc } from "./lib/api"; import { Language } from "./lib/types"; +import ArticleSelector from "./components/ArticleSelector"; +import Panel from "./components/Panel"; +import TOC from "./components/TOC"; + +import useUIStore from "./store/uiStore"; + +import "./App.css"; + type Props = { celexId: string; articleId: number; @@ -16,10 +19,7 @@ type Props = { function App({ celexId, articleId }: Props) { const navigate = useNavigate(); - const [numPanels, setNumPanels] = useState(1); - const [selectedParagraphId, setSelectedParagraphId] = useState( - null - ); + const { numPanels, addPanel } = useUIStore(); const results = useQueries({ queries: [ @@ -76,9 +76,7 @@ function App({ celexId, articleId }: Props) { selectedId={articleId} onSelected={(id) => navigate(`/${celexId}/articles/${id}`)} /> - +
))}
diff --git a/frontend/src/components/Panel.tsx b/frontend/src/components/Panel.tsx index ad09231..7bd32f4 100644 --- a/frontend/src/components/Panel.tsx +++ b/frontend/src/components/Panel.tsx @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; import { getArticle } from "../lib/api"; import { Language } from "../lib/types"; +import useUIStore from "../store/uiStore"; import LanguageSwitcher from "./LanguageSwitcher"; import "./Panel.css"; @@ -9,17 +10,11 @@ type PanelProps = { celexId: string; language?: Language; articleId: number; - selectedParagraphId?: string; - onParagraphSelected(paragraphId: string): void; }; -function Panel({ - celexId, - language, - articleId, - onParagraphSelected, - selectedParagraphId, -}: PanelProps) { +function Panel({ celexId, language, articleId }: PanelProps) { + const { selectedParagraphId, setSelectedParagraphId } = useUIStore(); + const [lang, setLang] = useState(language || Language.ENG); const articleRef = useRef(null); const { data, isPending, error } = useQuery({ @@ -39,11 +34,11 @@ function Panel({ p.classList.remove("highlight"); }); if (selectedParagraphId) { - const selectedParagraph = Array.from(paragraphs).find( + const el = Array.from(paragraphs).find( (p) => p.getAttribute("data-paragraph-id") === selectedParagraphId ); - if (selectedParagraph) { - selectedParagraph.classList.add("highlight"); + if (el) { + el.classList.add("highlight"); } } @@ -58,7 +53,7 @@ function Panel({ return; } target.classList.add("highlight"); - onParagraphSelected(paragraphId); + setSelectedParagraphId(paragraphId); }; paragraphs.forEach((element) => { @@ -72,7 +67,7 @@ function Panel({ element.removeEventListener("click", handleClick(element)); }); }; - }, [articleRef, data, selectedParagraphId, onParagraphSelected]); + }, [articleRef, data, selectedParagraphId, setSelectedParagraphId]); if (isPending) return "Loading..."; if (error) return "An error has occurred: " + error.message; diff --git a/frontend/src/store/uiStore.ts b/frontend/src/store/uiStore.ts new file mode 100644 index 0000000..4f322fc --- /dev/null +++ b/frontend/src/store/uiStore.ts @@ -0,0 +1,21 @@ +import { create } from "zustand"; + +interface UIState { + numPanels: number; + addPanel: () => void; + removePanel: () => void; + + selectedParagraphId: string | null; + setSelectedParagraphId: (selectedParagraphId: string | null) => void; +} +const useUIStore = create((set) => ({ + numPanels: 1, + selectedParagraphId: null, + addPanel: () => set((state) => ({ numPanels: state.numPanels + 1 })), + removePanel: () => + set((state) => ({ numPanels: Math.max(state.numPanels - 1, 1) })), + setSelectedParagraphId: (selectedParagraphId: string | null) => + set({ selectedParagraphId }), +})); + +export default useUIStore;