mirror of https://github.com/usememos/memos.git
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: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5828f34aae
commit
be7ef74698
|
|
@ -50,7 +50,7 @@ const MemoCommentMessage = observer(({ notification }: Props) => {
|
|||
});
|
||||
setCommentMemo(comment);
|
||||
|
||||
const sender = await userStore.getOrFetchUserByName(notification.sender);
|
||||
const sender = await userStore.getOrFetchUser(notification.sender);
|
||||
setSender(sender);
|
||||
setInitialized(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const useReactionGroups = (reactions: Reaction[]): ReactionGroup => {
|
|||
const fetchReactionGroups = async () => {
|
||||
const newReactionGroup = new Map<string, User[]>();
|
||||
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) || [];
|
||||
users.push(user);
|
||||
newReactionGroup.set(reaction.reactionType, uniq(users));
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ export const useMemoCreator = (creatorName: string) => {
|
|||
if (fetchedRef.current) return;
|
||||
fetchedRef.current = true;
|
||||
(async () => {
|
||||
const user = await userStore.getOrFetchUserByName(creatorName);
|
||||
const user = await userStore.getOrFetchUser(creatorName);
|
||||
setCreator(user);
|
||||
})();
|
||||
}, [creatorName]);
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ const PagedMemoList = observer((props: Props) => {
|
|||
// This significantly improves perceived performance by pre-populating the cache
|
||||
if (response?.memos && props.showCreator) {
|
||||
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 {
|
||||
setIsRequesting(false);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const MainLayout = observer(() => {
|
|||
// Fetch or get user to obtain user name (e.g., "users/123")
|
||||
// Note: User stats will be fetched by useFilteredMemoStats
|
||||
userStore
|
||||
.getOrFetchUserByUsername(username)
|
||||
.getOrFetchUser(`users/${username}`)
|
||||
.then((user) => {
|
||||
setProfileUserName(user.name);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const UserProfile = observer(() => {
|
|||
}
|
||||
|
||||
userStore
|
||||
.getOrFetchUserByUsername(username)
|
||||
.getOrFetchUser(`users/${username}`)
|
||||
.then((user) => {
|
||||
setUser(user);
|
||||
loadingState.setFinish();
|
||||
|
|
|
|||
|
|
@ -72,44 +72,28 @@ const userStore = (() => {
|
|||
const state = new LocalState();
|
||||
const deduplicator = new RequestDeduplicator();
|
||||
|
||||
const getOrFetchUserByName = async (name: string) => {
|
||||
const getOrFetchUser = async (name: string) => {
|
||||
const userMap = state.userMapByName;
|
||||
if (userMap[name]) {
|
||||
return userMap[name] as User;
|
||||
}
|
||||
const user = await userServiceClient.getUser({
|
||||
name: name,
|
||||
});
|
||||
state.setPartial({
|
||||
userMapByName: {
|
||||
...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 requestKey = createRequestKey("getOrFetchUser", { name });
|
||||
return deduplicator.execute(requestKey, async () => {
|
||||
// Double-check cache in case another request finished first
|
||||
if (state.userMapByName[name]) {
|
||||
return state.userMapByName[name] as User;
|
||||
}
|
||||
}
|
||||
// Use GetUser with username - supports both "users/{id}" and "users/{username}"
|
||||
const user = await userServiceClient.getUser({
|
||||
name: `users/${username}`,
|
||||
const user = await userServiceClient.getUser({
|
||||
name: name,
|
||||
});
|
||||
state.setPartial({
|
||||
userMapByName: {
|
||||
...state.userMapByName,
|
||||
[name]: user,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error(`User with username ${username} not found`);
|
||||
}
|
||||
state.setPartial({
|
||||
userMapByName: {
|
||||
...userMap,
|
||||
[user.name]: user,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
const getUserByName = (name: string) => {
|
||||
|
|
@ -292,8 +276,7 @@ const userStore = (() => {
|
|||
|
||||
return {
|
||||
state,
|
||||
getOrFetchUserByName,
|
||||
getOrFetchUserByUsername,
|
||||
getOrFetchUser,
|
||||
getUserByName,
|
||||
fetchUsers,
|
||||
updateUser,
|
||||
|
|
|
|||
Loading…
Reference in New Issue