Skip to content

Commit

Permalink
replace react state mangement by zustand
Browse files Browse the repository at this point in the history
  • Loading branch information
gaelph committed Dec 4, 2023
1 parent 87d9813 commit 5fb3510
Show file tree
Hide file tree
Showing 28 changed files with 445 additions and 842 deletions.
2 changes: 1 addition & 1 deletion src/client/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const buildOptions = {
bundle: true,
minify: true,
outfile: "build/static/main.js",
format: "esm",
format: "cjs",
metafile: true,
treeShaking: true,
sourcemap: true,
Expand Down
56 changes: 51 additions & 5 deletions src/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"heatmap.js": "^2.0.5",
"immer": "^10.0.3",
"keystats-common": "file:../common",
"material-symbols": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"zustand": "^4.4.7"
},
"scripts": {
"start": "PORT=6001 node esbuild.js -s",
Expand Down
19 changes: 9 additions & 10 deletions src/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ import "./App.css";
import KeymapsComponent from "~/components/Keymaps.js";
import StatsComponent from "~/components/Stats.js";
import Dates from "~/components/Dates.js";
import ApplicationStateProvider from "./state/appState.js";
import { useStoreInit } from "./state/appState.js";
import Header from "./components/Header.js";

function App() {
useStoreInit();
return (
<ApplicationStateProvider>
<div className="App keystats">
<Header />
<Dates />
<div className="content-container">
<KeymapsComponent />
<StatsComponent />
</div>
<div className="App keystats">
<Header />
<Dates />
<div className="content-container">
<KeymapsComponent />
<StatsComponent />
</div>
</ApplicationStateProvider>
</div>
);
}

Expand Down
7 changes: 3 additions & 4 deletions src/client/src/components/Dates.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState, useCallback, useRef, useEffect } from "react";
import React, { useState, useCallback, useRef } from "react";
import dayjs from "dayjs";
import dayjsen from "dayjs/locale/en.js";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js";
import { useDatesActions, useDatesContext } from "~/state/date.js";

import * as classes from "./Dates.module.css";
import useDates from "~/hooks/useDates.js";

dayjs.extend(isSameOrBefore);
dayjs.locale("en-europe", { ...dayjsen, weekStart: 1 });
Expand Down Expand Up @@ -261,8 +261,7 @@ function DatePicker({
}

export default function Dates(): React.ReactElement | null {
const { dates, date: selectedDate } = useDatesContext();
const { setDate } = useDatesActions();
const { date: selectedDate, dates, setDate } = useDates();

if (!dates) return null;

Expand Down
18 changes: 6 additions & 12 deletions src/client/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import React, { useEffect } from "react";
import { useFetchContext } from "~/state/fetch.js";
import { useKeyboardActions, useKeyboardContext } from "~/state/keyboard.js";
import { useKeyboardDataActions } from "~/state/keyboardData.js";
import { useKeyboardsContext } from "~/state/keyboards.js";
import KeyboardSelector from "./KeyboardSelector.js";

import useKeyboards from "~/hooks/useKeyboards.js";
import useFetchStatus from "~/hooks/useFetchStatus.js";

import * as classes from "./Header.module.css";

export default function Header(): React.ReactElement {
const { pendingCount, errors } = useFetchContext();
const keyboards = useKeyboardsContext();
const keyboard = useKeyboardContext();
const { setKeyboard } = useKeyboardActions();
const { refresh } = useKeyboardDataActions();
const { loading, errors, refresh } = useFetchStatus();
const { keyboard, keyboards, setKeyboard } = useKeyboards();

// Set the first keyboard of the list as the default
// TODO: use LocalStorage to keep that setting between page reloads
// If there were non in the storage
useEffect(() => {
if (!keyboard && keyboards && keyboards.length !== 0) {
setKeyboard(keyboards[0]);
}
}, [keyboard, keyboards]);

const loading = pendingCount !== 0;

return (
<header>
<div>
Expand Down
6 changes: 5 additions & 1 deletion src/client/src/components/Heatmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ export default function HeatmapComponent({
// },
});

heatmap.current.setData(formatData(matrix, total));
try {
heatmap.current.setData(formatData(matrix, total));
} catch (error) {
console.error(error);
}
}
}, [matrix, total]);

Expand Down
25 changes: 12 additions & 13 deletions src/client/src/components/KeyboardSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ export default function KeyboardSelector({
keyboards,
onChange,
}: KeyboardSelectorProps): React.ReactElement<KeyboardSelectorProps> {
const self = useRef<HTMLDivElement | null>(null);
const kMap = useRef<Map<number, HTMLDivElement>>(new Map());
const options = useRef<Array<HTMLDivElement>>([]);
const self = useRef<HTMLButtonElement | null>(null);
const options = useRef<Array<HTMLButtonElement>>([]);
const [visible, setVisible] = useState(false);

const [hovered, setHovered] = useState<number | null>(null);

useEffect(() => {
if (visible) {
const item = self.current?.querySelector(
const item = self.current?.querySelector<HTMLElement>(
"[role='menuitem']:not([tabindex='-1'])",
);
item?.focus();
Expand All @@ -32,14 +31,14 @@ export default function KeyboardSelector({
}, [selectedKeyboard, visible]);

const onKeyUp = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
(event: React.KeyboardEvent<HTMLButtonElement>) => {
console.log(event);
event.preventDefault();
event.stopPropagation();
const el = event.target as HTMLDivElement;
const kb = keyboards.find((kb) => kb.id === hovered);
const index = keyboards.indexOf(kb);
const item = self.current?.querySelector(
const index = keyboards.indexOf(kb!);
const item = self.current?.querySelector<HTMLElement>(
"[role='menuitem']:not([tabindex='-1'])",
);

Expand Down Expand Up @@ -91,7 +90,7 @@ export default function KeyboardSelector({

const menuItemOnKeyUp = useCallback(
(kb: Keyboard) => {
return (event: React.KeyboardEvent<HTMLDivElement>) => {
return (event: React.KeyboardEvent<HTMLElement>) => {
console.log("MI PRESS", event);
event.preventDefault();
event.stopPropagation();
Expand All @@ -111,11 +110,11 @@ export default function KeyboardSelector({
className={classes.keyboardSelector}
tabIndex={0}
role="listbox"
onClick={(e) => {
if (e.target.getAttribute("role") !== "menuitem") {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
const target = e.target as HTMLButtonElement;
if (target.getAttribute("role") !== "menuitem") {
setVisible(true);
// self.current?.focus();
const item = self.current?.querySelector(
const item = self.current?.querySelector<HTMLElement>(
"[role='menuitem']:not([tabindex='-1'])",
);
item?.focus();
Expand Down Expand Up @@ -150,7 +149,7 @@ export default function KeyboardSelector({
setHovered(kb.id);
}}
onMouseOut={() => {
setHovered(selectedKeyboard?.id);
setHovered(selectedKeyboard?.id || null);
}}
onClick={() => {
onChange(kb);
Expand Down
6 changes: 4 additions & 2 deletions src/client/src/components/Keymaps.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from "react";
import { useKeyboardData } from "~/state/keyboardData.js";
import useCounts from "~/hooks/useCounts.js";
import useKeymaps from "~/hooks/useKeymaps.js";
import HeatmapComponent from "./Heatmap.js";

import * as classes from "./Keymaps.module.css";

export default function Keymaps() {
const { counts, keymaps } = useKeyboardData();
const { counts } = useCounts();
const { keymaps } = useKeymaps();

return (
<div className={classes.layerContainer}>
Expand Down
16 changes: 5 additions & 11 deletions src/client/src/components/Stats.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React, { useCallback, useMemo } from "react";
import { Tabs, Tab } from "./Tabs.js";
import IconOrChar from "./IconOrChar.js";
import {
Character,
// RepetitionsBody,
// TotalCountBody,
} from "keystats-common/dto/keyboard";
import { useKeyboardData } from "~/state/keyboardData.js";
import { Character } from "keystats-common/dto/keyboard";

import * as classes from "./Stats.module.css";
import useCharacters from "~/hooks/useCharacters.js";
import useCounts from "~/hooks/useCounts.js";

const numberFormater = new Intl.NumberFormat("en-US", {});
function formatNumber(n: number): string {
Expand All @@ -33,11 +30,8 @@ const ROW_NAMES = ["Top row", "Home row", "Bottom row", "Thumb row"];
const p = (v: number, total: number): string => ((100 * v) / total).toFixed(2);

export default function StatsComponent(): React.ReactElement {
const {
counts: totals,
characters,
handAndFingerUsage: repetitions,
} = useKeyboardData();
const { characters } = useCharacters();
const { counts: totals, handAndFingerUsage: repetitions } = useCounts();
const { totalKeypresses } = totals;
let records: Character[] = [];
let totalCharacters = 0;
Expand Down
46 changes: 5 additions & 41 deletions src/client/src/hooks/useCharacters.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,8 @@
import dayjs from "dayjs";
import { useCallback, useEffect, useState } from "react";
import { useAppState } from "~/state/appState.js";
import { KeyboardDataState } from "~/state/keyboardData.js";

import { getCharacterCounts, Keyboard } from "../lib/api.js";
import { FilterQuery } from "keystats-common/dto/keyboard";
import { useFetchActions } from "~/state/fetch.js";
export default function useCharacters(): Pick<KeyboardDataState, "characters"> {
const characters = useAppState((state) => state.characters);

type Data = Awaited<ReturnType<typeof getCharacterCounts>>;

export default function useCharacters(
keyboard: Keyboard | null,
date?: dayjs.Dayjs | null,
): [Data | null, () => Promise<void>] {
const [state, setState] = useState<Data | null>(null);
const { setLoading, addError } = useFetchActions();

const fetchData = useCallback(async () => {
if (!keyboard) {
return;
}
setLoading(true);
try {
const filters: FilterQuery = {};
if (date) {
filters.date = date;
}
const data = await getCharacterCounts(keyboard.id, filters);
setState(data);
} catch (error) {
console.error(error);
if (error instanceof Error) {
addError(error);
}
} finally {
setLoading(false);
}
}, [date, keyboard]);

useEffect(() => {
fetchData();
}, [fetchData]);

return [state, fetchData];
return { characters };
}
Loading

0 comments on commit 5fb3510

Please sign in to comment.