Commit be7ef746 authored by Steven's avatar Steven

fix: eliminate duplicate API requests by deduplicating user fetch calls

- Add request deduplication to getOrFetchUser using RequestDeduplicator
- Consolidates multiple simultaneous calls for same user into single API request
- Prevents duplicate 401 errors and wasted network traffic
- Matches pattern already used by fetchUsers and fetchUserStats
- Remove backwards compatibility aliases (getOrFetchUserByName, getOrFetchUserByUsername)
- Update all call sites to use canonical getOrFetchUser method

Fixes issue where PagedMemoList, useMemoViewState, MainLayout, and UserProfile
were making duplicate user fetch requests when loading user data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: 's avatarClaude Haiku 4.5 <noreply@anthropic.com>
parent 5828f34a
...@@ -50,7 +50,7 @@ const MemoCommentMessage = observer(({ notification }: Props) => { ...@@ -50,7 +50,7 @@ const MemoCommentMessage = observer(({ notification }: Props) => {
}); });
setCommentMemo(comment); setCommentMemo(comment);
const sender = await userStore.getOrFetchUserByName(notification.sender); const sender = await userStore.getOrFetchUser(notification.sender);
setSender(sender); setSender(sender);
setInitialized(true); setInitialized(true);
} }
......
...@@ -15,7 +15,7 @@ export const useReactionGroups = (reactions: Reaction[]): ReactionGroup => { ...@@ -15,7 +15,7 @@ export const useReactionGroups = (reactions: Reaction[]): ReactionGroup => {
const fetchReactionGroups = async () => { const fetchReactionGroups = async () => {
const newReactionGroup = new Map<string, User[]>(); const newReactionGroup = new Map<string, User[]>();
for (const reaction of reactions) { for (const reaction of reactions) {
const user = await userStore.getOrFetchUserByName(reaction.creator); const user = await userStore.getOrFetchUser(reaction.creator);
const users = newReactionGroup.get(reaction.reactionType) || []; const users = newReactionGroup.get(reaction.reactionType) || [];
users.push(user); users.push(user);
newReactionGroup.set(reaction.reactionType, uniq(users)); newReactionGroup.set(reaction.reactionType, uniq(users));
......
...@@ -116,7 +116,7 @@ export const useMemoCreator = (creatorName: string) => { ...@@ -116,7 +116,7 @@ export const useMemoCreator = (creatorName: string) => {
if (fetchedRef.current) return; if (fetchedRef.current) return;
fetchedRef.current = true; fetchedRef.current = true;
(async () => { (async () => {
const user = await userStore.getOrFetchUserByName(creatorName); const user = await userStore.getOrFetchUser(creatorName);
setCreator(user); setCreator(user);
})(); })();
}, [creatorName]); }, [creatorName]);
......
...@@ -67,7 +67,7 @@ const PagedMemoList = observer((props: Props) => { ...@@ -67,7 +67,7 @@ const PagedMemoList = observer((props: Props) => {
// This significantly improves perceived performance by pre-populating the cache // This significantly improves perceived performance by pre-populating the cache
if (response?.memos && props.showCreator) { if (response?.memos && props.showCreator) {
const uniqueCreators = Array.from(new Set(response.memos.map((memo) => memo.creator))); const uniqueCreators = Array.from(new Set(response.memos.map((memo) => memo.creator)));
await Promise.allSettled(uniqueCreators.map((creator) => userStore.getOrFetchUserByName(creator))); await Promise.allSettled(uniqueCreators.map((creator) => userStore.getOrFetchUser(creator)));
} }
} finally { } finally {
setIsRequesting(false); setIsRequesting(false);
......
...@@ -35,7 +35,7 @@ const MainLayout = observer(() => { ...@@ -35,7 +35,7 @@ const MainLayout = observer(() => {
// Fetch or get user to obtain user name (e.g., "users/123") // Fetch or get user to obtain user name (e.g., "users/123")
// Note: User stats will be fetched by useFilteredMemoStats // Note: User stats will be fetched by useFilteredMemoStats
userStore userStore
.getOrFetchUserByUsername(username) .getOrFetchUser(`users/${username}`)
.then((user) => { .then((user) => {
setProfileUserName(user.name); setProfileUserName(user.name);
}) })
......
...@@ -30,7 +30,7 @@ const UserProfile = observer(() => { ...@@ -30,7 +30,7 @@ const UserProfile = observer(() => {
} }
userStore userStore
.getOrFetchUserByUsername(username) .getOrFetchUser(`users/${username}`)
.then((user) => { .then((user) => {
setUser(user); setUser(user);
loadingState.setFinish(); loadingState.setFinish();
......
...@@ -72,44 +72,28 @@ const userStore = (() => { ...@@ -72,44 +72,28 @@ const userStore = (() => {
const state = new LocalState(); const state = new LocalState();
const deduplicator = new RequestDeduplicator(); const deduplicator = new RequestDeduplicator();
const getOrFetchUserByName = async (name: string) => { const getOrFetchUser = async (name: string) => {
const userMap = state.userMapByName; const userMap = state.userMapByName;
if (userMap[name]) { if (userMap[name]) {
return userMap[name] as User; return userMap[name] as User;
} }
const user = await userServiceClient.getUser({ const requestKey = createRequestKey("getOrFetchUser", { name });
name: name, return deduplicator.execute(requestKey, async () => {
}); // Double-check cache in case another request finished first
state.setPartial({ if (state.userMapByName[name]) {
userMapByName: { return state.userMapByName[name] as User;
...userMap,
[name]: user,
},
});
return user;
};
const getOrFetchUserByUsername = async (username: string) => {
const userMap = state.userMapByName;
for (const name in userMap) {
if (userMap[name].username === username) {
return userMap[name];
} }
} const user = await userServiceClient.getUser({
// Use GetUser with username - supports both "users/{id}" and "users/{username}" name: name,
const user = await userServiceClient.getUser({ });
name: `users/${username}`, state.setPartial({
}); userMapByName: {
if (!user) { ...state.userMapByName,
throw new Error(`User with username ${username} not found`); [name]: user,
} },
state.setPartial({ });
userMapByName: { return user;
...userMap,
[user.name]: user,
},
}); });
return user;
}; };
const getUserByName = (name: string) => { const getUserByName = (name: string) => {
...@@ -292,8 +276,7 @@ const userStore = (() => { ...@@ -292,8 +276,7 @@ const userStore = (() => {
return { return {
state, state,
getOrFetchUserByName, getOrFetchUser,
getOrFetchUserByUsername,
getUserByName, getUserByName,
fetchUsers, fetchUsers,
updateUser, updateUser,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment