Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
955ff0ca
Commit
955ff0ca
authored
Dec 28, 2025
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: optimize user fetching in MemoCommentMessage and MemoReactionListView components
parent
115d1bac
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
69 additions
and
36 deletions
+69
-36
MemoCommentMessage.tsx
web/src/components/Inbox/MemoCommentMessage.tsx
+6
-7
hooks.ts
web/src/components/MemoReactionListView/hooks.ts
+16
-19
PagedMemoList.tsx
web/src/components/PagedMemoList/PagedMemoList.tsx
+16
-10
useUserQueries.ts
web/src/hooks/useUserQueries.ts
+31
-0
No files found.
web/src/components/Inbox/MemoCommentMessage.tsx
View file @
955ff0ca
...
...
@@ -8,10 +8,11 @@ import { activityServiceClient, memoServiceClient, userServiceClient } from "@/c
import
{
activityNamePrefix
}
from
"@/helpers/resource-names"
;
import
useAsyncEffect
from
"@/hooks/useAsyncEffect"
;
import
useNavigateTo
from
"@/hooks/useNavigateTo"
;
import
{
useUser
}
from
"@/hooks/useUserQueries"
;
import
{
handleError
}
from
"@/lib/error"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
{
User
,
User
Notification
,
UserNotification_Status
}
from
"@/types/proto/api/v1/user_service_pb"
;
import
{
UserNotification
,
UserNotification_Status
}
from
"@/types/proto/api/v1/user_service_pb"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
interface
Props
{
...
...
@@ -23,10 +24,12 @@ function MemoCommentMessage({ notification }: Props) {
const
navigateTo
=
useNavigateTo
();
const
[
relatedMemo
,
setRelatedMemo
]
=
useState
<
Memo
|
undefined
>
(
undefined
);
const
[
commentMemo
,
setCommentMemo
]
=
useState
<
Memo
|
undefined
>
(
undefined
);
const
[
sender
,
setSender
]
=
useState
<
User
|
undefined
>
(
undefined
);
const
[
sender
Name
,
setSenderName
]
=
useState
<
string
|
undefined
>
(
undefined
);
const
[
initialized
,
setInitialized
]
=
useState
<
boolean
>
(
false
);
const
[
hasError
,
setHasError
]
=
useState
<
boolean
>
(
false
);
const
{
data
:
sender
}
=
useUser
(
senderName
||
""
,
{
enabled
:
!!
senderName
});
useAsyncEffect
(
async
()
=>
{
if
(
!
notification
.
activityId
)
{
return
;
...
...
@@ -44,16 +47,12 @@ function MemoCommentMessage({ notification }: Props) {
});
setRelatedMemo
(
memo
);
// Fetch the comment memo
const
comment
=
await
memoServiceClient
.
getMemo
({
name
:
memoCommentPayload
.
memo
,
});
setCommentMemo
(
comment
);
const
sender
=
await
userServiceClient
.
getUser
({
name
:
notification
.
sender
,
});
setSender
(
sender
);
setSenderName
(
notification
.
sender
);
setInitialized
(
true
);
}
}
catch
(
error
)
{
...
...
web/src/components/MemoReactionListView/hooks.ts
View file @
955ff0ca
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
uniq
}
from
"lodash-es"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
memoServiceClient
,
userServiceClient
}
from
"@/connect"
;
import
{
useMemo
}
from
"react"
;
import
{
memoServiceClient
}
from
"@/connect"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
memoKeys
}
from
"@/hooks/useMemoQueries"
;
import
{
useUsersByNames
}
from
"@/hooks/useUserQueries"
;
import
type
{
Memo
,
Reaction
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
type
{
User
}
from
"@/types/proto/api/v1/user_service_pb"
;
export
type
ReactionGroup
=
Map
<
string
,
User
[]
>
;
export
const
useReactionGroups
=
(
reactions
:
Reaction
[]):
ReactionGroup
=>
{
const
[
reactionGroup
,
setReactionGroup
]
=
useState
<
ReactionGroup
>
(
new
Map
());
const
creatorNames
=
useMemo
(()
=>
reactions
.
map
((
r
)
=>
r
.
creator
),
[
reactions
]);
const
{
data
:
userMap
}
=
useUsersByNames
(
creatorNames
);
useEffect
(()
=>
{
const
fetchReactionGroups
=
async
()
=>
{
const
newReactionGroup
=
new
Map
<
string
,
User
[]
>
();
for
(
const
reaction
of
reactions
)
{
// Fetch user via gRPC directly since we need it within an effect
const
user
=
await
userServiceClient
.
getUser
({
name
:
reaction
.
creator
});
const
users
=
newReactionGroup
.
get
(
reaction
.
reactionType
)
||
[];
users
.
push
(
user
);
newReactionGroup
.
set
(
reaction
.
reactionType
,
uniq
(
users
));
}
setReactionGroup
(
newReactionGroup
);
};
fetchReactionGroups
();
},
[
reactions
]);
return
useMemo
(()
=>
{
const
reactionGroup
=
new
Map
<
string
,
User
[]
>
();
for
(
const
reaction
of
reactions
)
{
const
user
=
userMap
?.
get
(
reaction
.
creator
);
if
(
!
user
)
continue
;
return
reactionGroup
;
const
users
=
reactionGroup
.
get
(
reaction
.
reactionType
)
||
[];
users
.
push
(
user
);
reactionGroup
.
set
(
reaction
.
reactionType
,
users
);
}
return
reactionGroup
;
},
[
reactions
,
userMap
]);
};
interface
UseReactionActionsOptions
{
...
...
web/src/components/PagedMemoList/PagedMemoList.tsx
View file @
955ff0ca
import
{
ArrowUpIcon
,
LoaderIcon
}
from
"lucide-react"
;
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
ArrowUpIcon
}
from
"lucide-react"
;
import
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
"react"
;
import
{
matchPath
}
from
"react-router-dom"
;
import
{
Button
}
from
"@/components/ui/button"
;
...
...
@@ -6,6 +7,7 @@ import { userServiceClient } from "@/connect";
import
{
useView
}
from
"@/contexts/ViewContext"
;
import
{
DEFAULT_LIST_MEMOS_PAGE_SIZE
}
from
"@/helpers/consts"
;
import
{
useInfiniteMemos
}
from
"@/hooks/useMemoQueries"
;
import
{
userKeys
}
from
"@/hooks/useUserQueries"
;
import
{
Routes
}
from
"@/router"
;
import
{
State
}
from
"@/types/proto/api/v1/common_pb"
;
import
type
{
Memo
}
from
"@/types/proto/api/v1/memo_service_pb"
;
...
...
@@ -81,6 +83,7 @@ function useAutoFetchWhenNotScrollable({
const
PagedMemoList
=
(
props
:
Props
)
=>
{
const
t
=
useTranslate
();
const
{
layout
}
=
useView
();
const
queryClient
=
useQueryClient
();
// Show memo editor only on the root route
const
showMemoEditor
=
Boolean
(
matchPath
(
Routes
.
ROOT
,
window
.
location
.
pathname
));
...
...
@@ -99,7 +102,7 @@ const PagedMemoList = (props: Props) => {
// Apply custom sorting if provided, otherwise use memos directly
const
sortedMemoList
=
useMemo
(()
=>
(
props
.
listSort
?
props
.
listSort
(
memos
)
:
memos
),
[
memos
,
props
.
listSort
]);
//
Batch-
fetch creators when new data arrives to improve performance
//
Pre
fetch creators when new data arrives to improve performance
useEffect
(()
=>
{
if
(
!
data
?.
pages
||
!
props
.
showCreator
)
return
;
...
...
@@ -107,14 +110,17 @@ const PagedMemoList = (props: Props) => {
if
(
!
lastPage
?.
memos
)
return
;
const
uniqueCreators
=
Array
.
from
(
new
Set
(
lastPage
.
memos
.
map
((
memo
)
=>
memo
.
creator
)));
void
Promise
.
allSettled
(
uniqueCreators
.
map
((
creator
)
=>
userServiceClient
.
getUser
({
name
:
creator
}).
catch
(()
=>
{
/* silently ignore errors */
}),
),
);
},
[
data
?.
pages
,
props
.
showCreator
]);
for
(
const
creator
of
uniqueCreators
)
{
void
queryClient
.
prefetchQuery
({
queryKey
:
userKeys
.
detail
(
creator
),
queryFn
:
async
()
=>
{
const
user
=
await
userServiceClient
.
getUser
({
name
:
creator
});
return
user
;
},
staleTime
:
1000
*
60
*
5
,
});
}
},
[
data
?.
pages
,
props
.
showCreator
,
queryClient
]);
// Auto-fetch hook: fetches more content when page isn't scrollable
useAutoFetchWhenNotScrollable
({
...
...
web/src/hooks/useUserQueries.ts
View file @
955ff0ca
...
...
@@ -15,6 +15,7 @@ export const userKeys = {
currentUser
:
()
=>
[...
userKeys
.
all
,
"current"
]
as
const
,
shortcuts
:
()
=>
[...
userKeys
.
all
,
"shortcuts"
]
as
const
,
notifications
:
()
=>
[...
userKeys
.
all
,
"notifications"
]
as
const
,
byNames
:
(
names
:
string
[])
=>
[...
userKeys
.
all
,
"byNames"
,
...
names
.
sort
()]
as
const
,
};
// NOTE: This hook is currently UNUSED in favor of the AuthContext-based
...
...
@@ -226,3 +227,33 @@ export function useUpdateUserGeneralSetting(currentUserName?: string) {
},
});
}
// Hook to fetch multiple users by names (returns Map<name, User>)
export
function
useUsersByNames
(
names
:
string
[])
{
const
enabled
=
names
.
length
>
0
;
const
uniqueNames
=
Array
.
from
(
new
Set
(
names
));
return
useQuery
({
queryKey
:
userKeys
.
byNames
(
uniqueNames
),
queryFn
:
async
()
=>
{
const
users
=
await
Promise
.
all
(
uniqueNames
.
map
(
async
(
name
)
=>
{
try
{
const
user
=
await
userServiceClient
.
getUser
({
name
});
return
{
name
,
user
};
}
catch
{
return
{
name
,
user
:
undefined
};
}
}),
);
const
userMap
=
new
Map
<
string
,
User
|
undefined
>
();
for
(
const
{
name
,
user
}
of
users
)
{
userMap
.
set
(
name
,
user
);
}
return
userMap
;
},
enabled
,
staleTime
:
1000
*
60
*
5
,
// 5 minutes - user profiles don't change often
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment