From 866937787c9438b390ddb3a8f9e5fa107ba52a6f Mon Sep 17 00:00:00 2001 From: Steven Date: Sun, 10 Sep 2023 11:43:38 +0800 Subject: [PATCH] chore: clean duplicated requests --- web/src/components/LocaleSelect.tsx | 2 +- web/src/components/Memo.tsx | 2 +- web/src/components/MemoList.tsx | 6 +++++- web/src/components/Settings/MemberSection.tsx | 18 +++++++++--------- web/src/components/UsageHeatMap.tsx | 8 +++++++- web/src/components/UserAvatar.tsx | 6 +++++- web/src/css/global.css | 3 --- web/src/pages/Auth.tsx | 4 ++-- web/src/pages/Home.tsx | 4 +++- web/src/pages/MemoDetail.tsx | 7 ++++--- web/src/pages/UserProfile.tsx | 6 ++++-- web/src/store/module/memo.ts | 9 +-------- web/src/store/module/user.ts | 12 +----------- web/src/store/reducer/memo.ts | 11 +---------- web/src/store/reducer/user.ts | 16 ++-------------- web/src/store/v1/user.ts | 11 ++++++++++- 16 files changed, 56 insertions(+), 69 deletions(-) diff --git a/web/src/components/LocaleSelect.tsx b/web/src/components/LocaleSelect.tsx index 1251ff88..8c19149d 100644 --- a/web/src/components/LocaleSelect.tsx +++ b/web/src/components/LocaleSelect.tsx @@ -5,8 +5,8 @@ import Icon from "./Icon"; interface Props { value: Locale; - onChange: (locale: Locale) => void; className?: string; + onChange: (locale: Locale) => void; } const LocaleSelect: FC = (props: Props) => { diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index 87730595..5aad3d4f 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -229,7 +229,7 @@ const Memo: React.FC = (props: Props) => { <> - {creator.nickname} + {creator.nickname} diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx index b2823460..90120346 100644 --- a/web/src/components/MemoList.tsx +++ b/web/src/components/MemoList.tsx @@ -15,7 +15,8 @@ const MemoList: React.FC = () => { const userStore = useUserStore(); const filterStore = useFilterStore(); const filter = filterStore.state; - const { memos, isFetching } = memoStore.state; + const { memos } = memoStore.state; + const [isFetching, setIsFetching] = useState(true); const [isComplete, setIsComplete] = useState(false); const currentUsername = userStore.getCurrentUsername(); @@ -82,6 +83,7 @@ const MemoList: React.FC = () => { } else { setIsComplete(false); } + setIsFetching(false); }) .catch((error) => { console.error(error); @@ -122,12 +124,14 @@ const MemoList: React.FC = () => { const handleFetchMoreClick = async () => { try { + setIsFetching(true); const fetchedMemos = await memoStore.fetchMemos(DEFAULT_MEMO_LIMIT, memos.length); if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) { setIsComplete(true); } else { setIsComplete(false); } + setIsFetching(false); } catch (error: any) { console.error(error); toast.error(error.response.data.message); diff --git a/web/src/components/Settings/MemberSection.tsx b/web/src/components/Settings/MemberSection.tsx index 695d6969..6aef6bf3 100644 --- a/web/src/components/Settings/MemberSection.tsx +++ b/web/src/components/Settings/MemberSection.tsx @@ -136,17 +136,17 @@ const PreferencesSection = () => {
- - + - - - @@ -155,13 +155,13 @@ const PreferencesSection = () => { {userList.map((user) => ( - - + - - + +
+
ID + {t("common.username")} + {t("common.nickname")} + {t("common.email")}
{user.id} + {user.id} {user.username} {user.rowStatus === "ARCHIVED" && "(Archived)"} {user.nickname}{user.email}{user.nickname}{user.email} {currentUser?.id === user.id ? ( {t("common.yourself")} diff --git a/web/src/components/UsageHeatMap.tsx b/web/src/components/UsageHeatMap.tsx index 154f504b..e83270b4 100644 --- a/web/src/components/UsageHeatMap.tsx +++ b/web/src/components/UsageHeatMap.tsx @@ -3,6 +3,7 @@ import { getMemoStats } from "@/helpers/api"; import { DAILY_TIMESTAMP } from "@/helpers/consts"; import { getDateStampByDate, getDateString, getTimeStampByDate } from "@/helpers/datetime"; import * as utils from "@/helpers/utils"; +import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; import { useFilterStore, useMemoStore, useUserStore } from "../store/module"; import "@/less/usage-heat-map.less"; @@ -32,6 +33,7 @@ const UsageHeatMap = () => { const t = useTranslate(); const filterStore = useFilterStore(); const userStore = useUserStore(); + const userV1Store = useUserV1Store(); const memoStore = useMemoStore(); const todayTimeStamp = getDateStampByDate(Date.now()); const todayDay = new Date(todayTimeStamp).getDay() + 1; @@ -47,7 +49,7 @@ const UsageHeatMap = () => { const currentUsername = userStore.getCurrentUsername(); useEffect(() => { - userStore.getUserByUsername(currentUsername).then((user) => { + userV1Store.getOrFetchUserByUsername(currentUsername).then((user) => { if (!user) { return; } @@ -56,6 +58,10 @@ const UsageHeatMap = () => { }, [currentUsername]); useEffect(() => { + if (memos.length === 0) { + return; + } + getMemoStats(currentUsername) .then(({ data }) => { setMemoAmount(data.length); diff --git a/web/src/components/UserAvatar.tsx b/web/src/components/UserAvatar.tsx index 4832d24e..ce9535f0 100644 --- a/web/src/components/UserAvatar.tsx +++ b/web/src/components/UserAvatar.tsx @@ -9,7 +9,11 @@ const UserAvatar = (props: Props) => { const { avatarUrl, className } = props; return (
- +
); }; diff --git a/web/src/css/global.css b/web/src/css/global.css index a29ddc72..e695920e 100644 --- a/web/src/css/global.css +++ b/web/src/css/global.css @@ -1,9 +1,6 @@ html, body { @apply text-base w-full h-full overflow-hidden dark:bg-zinc-800; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Noto Sans", - "Noto Sans CJK SC", "Microsoft YaHei UI", "Microsoft YaHei", "WenQuanYi Micro Hei", "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol", "Noto Color Emoji", sans-serif; } #root { diff --git a/web/src/pages/Auth.tsx b/web/src/pages/Auth.tsx index a990068e..7f95579f 100644 --- a/web/src/pages/Auth.tsx +++ b/web/src/pages/Auth.tsx @@ -143,7 +143,7 @@ const Auth = () => { className="w-full" size="lg" type="text" - disabled={actionBtnLoadingState.isLoading} + readOnly={actionBtnLoadingState.isLoading} placeholder={t("common.username")} value={username} onChange={handleUsernameInputChanged} @@ -153,7 +153,7 @@ const Auth = () => { className="w-full" size="lg" type="password" - disabled={actionBtnLoadingState.isLoading} + readOnly={actionBtnLoadingState.isLoading} placeholder={t("common.password")} value={password} onChange={handlePasswordInputChanged} diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index 1d473864..d6fe8815 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -6,17 +6,19 @@ import MemoFilter from "@/components/MemoFilter"; import MemoList from "@/components/MemoList"; import MobileHeader from "@/components/MobileHeader"; import { useGlobalStore, useUserStore } from "@/store/module"; +import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; const Home = () => { const t = useTranslate(); const globalStore = useGlobalStore(); const userStore = useUserStore(); + const userV1Store = useUserV1Store(); const user = userStore.state.user; useEffect(() => { const currentUsername = userStore.getCurrentUsername(); - userStore.getUserByUsername(currentUsername).catch((error) => { + userV1Store.getOrFetchUserByUsername(currentUsername).catch((error) => { console.error(error); toast.error(t("message.user-not-found")); }); diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index 949b44c8..48c442f4 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -5,12 +5,13 @@ import FloatingNavButton from "@/components/FloatingNavButton"; import Memo from "@/components/Memo"; import UserAvatar from "@/components/UserAvatar"; import useLoading from "@/hooks/useLoading"; -import { useMemoStore, useUserStore } from "@/store/module"; +import { useMemoStore } from "@/store/module"; +import { useUserV1Store } from "@/store/v1"; const MemoDetail = () => { const params = useParams(); const memoStore = useMemoStore(); - const userStore = useUserStore(); + const userV1Store = useUserV1Store(); const loadingState = useLoading(); const [user, setUser] = useState(); const memoId = Number(params.memoId); @@ -21,7 +22,7 @@ const MemoDetail = () => { memoStore .fetchMemoById(memoId) .then(async (memo) => { - const user = await userStore.getUserByUsername(memo.creatorUsername); + const user = await userV1Store.getOrFetchUserByUsername(memo.creatorUsername); setUser(user); loadingState.setFinish(); }) diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index 862be4b9..5d18dc48 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -6,18 +6,20 @@ import MemoList from "@/components/MemoList"; import UserAvatar from "@/components/UserAvatar"; import useLoading from "@/hooks/useLoading"; import { useUserStore } from "@/store/module"; +import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; const UserProfile = () => { const t = useTranslate(); const userStore = useUserStore(); + const userV1Store = useUserV1Store(); const loadingState = useLoading(); const [user, setUser] = useState(); useEffect(() => { const currentUsername = userStore.getCurrentUsername(); - userStore - .getUserByUsername(currentUsername) + userV1Store + .getOrFetchUserByUsername(currentUsername) .then((user) => { setUser(user); loadingState.setFinish(); diff --git a/web/src/store/module/memo.ts b/web/src/store/module/memo.ts index 111b9fff..e94280ba 100644 --- a/web/src/store/module/memo.ts +++ b/web/src/store/module/memo.ts @@ -2,7 +2,7 @@ import { omit } from "lodash-es"; import * as api from "@/helpers/api"; import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; import store, { useAppSelector } from "../"; -import { createMemo, deleteMemo, patchMemo, setIsFetching, upsertMemos } from "../reducer/memo"; +import { createMemo, deleteMemo, patchMemo, upsertMemos } from "../reducer/memo"; import { useMemoCacheStore } from "../v1"; import { useUserStore } from "./"; @@ -34,7 +34,6 @@ export const useMemoStore = () => { return store.getState().memo; }, fetchMemos: async (limit = DEFAULT_MEMO_LIMIT, offset = 0) => { - store.dispatch(setIsFetching(true)); const memoFind: MemoFind = { rowStatus: "NORMAL", limit, @@ -46,26 +45,20 @@ export const useMemoStore = () => { const { data } = await api.getMemoList(memoFind); const fetchedMemos = data.map((m) => convertResponseModelMemo(m)); store.dispatch(upsertMemos(fetchedMemos)); - store.dispatch(setIsFetching(false)); - for (const m of fetchedMemos) { memoCacheStore.setMemoCache(m); } - return fetchedMemos; }, fetchAllMemos: async (limit = DEFAULT_MEMO_LIMIT, offset?: number) => { - store.dispatch(setIsFetching(true)); const memoFind: MemoFind = { rowStatus: "NORMAL", limit, offset, }; - const { data } = await api.getAllMemos(memoFind); const fetchedMemos = data.map((m) => convertResponseModelMemo(m)); store.dispatch(upsertMemos(fetchedMemos)); - store.dispatch(setIsFetching(false)); for (const m of fetchedMemos) { memoCacheStore.setMemoCache(m); diff --git a/web/src/store/module/user.ts b/web/src/store/module/user.ts index 34f01968..022fdade 100644 --- a/web/src/store/module/user.ts +++ b/web/src/store/module/user.ts @@ -5,7 +5,7 @@ import storage from "@/helpers/storage"; import { getSystemColorScheme } from "@/helpers/utils"; import store, { useAppSelector } from ".."; import { setAppearance, setLocale } from "../reducer/global"; -import { patchUser, setHost, setUser, setUserById } from "../reducer/user"; +import { patchUser, setHost, setUser } from "../reducer/user"; const defaultSetting: Setting = { locale: "en", @@ -118,16 +118,6 @@ export const useUserStore = () => { return state.user?.username || UNKNOWN_USERNAME; } }, - getUserByUsername: async (username: string) => { - const { data } = await api.getUserByUsername(username); - if (data) { - const user = convertResponseModelUser(data); - store.dispatch(setUserById(user)); - return user; - } else { - return undefined; - } - }, upsertUserSetting: async (key: string, value: any) => { await api.upsertUserSetting({ key: key as any, diff --git a/web/src/store/reducer/memo.ts b/web/src/store/reducer/memo.ts index be003911..c4aaa812 100644 --- a/web/src/store/reducer/memo.ts +++ b/web/src/store/reducer/memo.ts @@ -3,15 +3,12 @@ import { uniqBy } from "lodash-es"; interface State { memos: Memo[]; - isFetching: boolean; } const memoSlice = createSlice({ name: "memo", initialState: { memos: [], - // isFetching flag should starts with true. - isFetching: true, } as State, reducers: { upsertMemos: (state, action: PayloadAction) => { @@ -51,15 +48,9 @@ const memoSlice = createSlice({ }), }; }, - setIsFetching: (state, action: PayloadAction) => { - return { - ...state, - isFetching: action.payload, - }; - }, }, }); -export const { upsertMemos, createMemo, patchMemo, deleteMemo, setIsFetching } = memoSlice.actions; +export const { upsertMemos, createMemo, patchMemo, deleteMemo } = memoSlice.actions; export default memoSlice.reducer; diff --git a/web/src/store/reducer/user.ts b/web/src/store/reducer/user.ts index b4b59a72..57cae6d3 100644 --- a/web/src/store/reducer/user.ts +++ b/web/src/store/reducer/user.ts @@ -1,19 +1,15 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { cloneDeep } from "lodash-es"; interface State { // host is the user who hist the system host?: User; // user is the user who is currently logged in user?: User; - userById: { [key: UserId]: User }; } const userSlice = createSlice({ name: "user", - initialState: { - userById: {}, - } as State, + initialState: {} as State, reducers: { setHost: (state, action: PayloadAction) => { return { @@ -27,14 +23,6 @@ const userSlice = createSlice({ user: action.payload, }; }, - setUserById: (state, action: PayloadAction) => { - const userById = cloneDeep(state.userById); - userById[action.payload.id] = action.payload; - return { - ...state, - userById: userById, - }; - }, patchUser: (state, action: PayloadAction>) => { return { ...state, @@ -47,6 +35,6 @@ const userSlice = createSlice({ }, }); -export const { setHost, setUser, setUserById, patchUser } = userSlice.actions; +export const { setHost, setUser, patchUser } = userSlice.actions; export default userSlice.reducer; diff --git a/web/src/store/v1/user.ts b/web/src/store/v1/user.ts index 86d42d7d..9d72670e 100644 --- a/web/src/store/v1/user.ts +++ b/web/src/store/v1/user.ts @@ -8,6 +8,9 @@ interface UserV1Store { getUserByUsername: (username: string) => User; } +// Request cache is used to prevent multiple requests. +const requestCache = new Map>(); + const useUserV1Store = create()((set, get) => ({ userMapByUsername: {}, getOrFetchUserByUsername: async (username: string) => { @@ -15,8 +18,14 @@ const useUserV1Store = create()((set, get) => ({ if (userMap[username]) { return userMap[username] as User; } + if (requestCache.has(username)) { + return await requestCache.get(username); + } - const { data } = await api.getUserByUsername(username); + const promise = api.getUserByUsername(username); + requestCache.set(username, promise); + const { data } = await promise; + requestCache.delete(username); const user = convertResponseModelUser(data); userMap[username] = user; set(userMap);