@@ -15,7 +15,7 @@ import {
1515 Tooltip ,
1616} from "@dub/ui" ;
1717import { cn } from "@dub/utils" ;
18- import { memo , useEffect , useMemo , useState } from "react" ;
18+ import { memo , useEffect , useMemo , useRef , useState } from "react" ;
1919import { useFormContext , useWatch } from "react-hook-form" ;
2020import { toast } from "sonner" ;
2121import { mutate } from "swr" ;
@@ -61,6 +61,16 @@ export const TagSelect = memo(() => {
6161 } ) ;
6262 const [ debouncedUrl ] = useDebounce ( url , 500 ) ;
6363
64+ const tagResolutionCacheRef = useRef < Map < string , TagProps > > ( new Map ( ) ) ;
65+ const prevLinkIdRef = useRef < string | undefined > ( undefined ) ;
66+ if ( linkId !== prevLinkIdRef . current ) {
67+ tagResolutionCacheRef . current = new Map ( ) ;
68+ prevLinkIdRef . current = linkId ;
69+ }
70+ for ( const t of [ ...( tags ?? [ ] ) , ...( availableTags ?? [ ] ) ] ) {
71+ if ( t ?. id ) tagResolutionCacheRef . current . set ( t . id , t ) ;
72+ }
73+
6474 const [ isOpen , setIsOpen ] = useState ( false ) ;
6575
6676 const createTag = async ( tag : string ) => {
@@ -87,15 +97,23 @@ export const TagSelect = memo(() => {
8797 return false ;
8898 } ;
8999
90- const options = useMemo (
91- ( ) => availableTags ?. map ( ( tag ) => getTagOption ( tag ) ) ,
92- [ availableTags ] ,
93- ) ;
100+ const options = useMemo ( ( ) => {
101+ if ( loadingTags || availableTags === undefined ) return undefined ;
102+ const apiIds = new Set ( availableTags . map ( ( t ) => t . id ) ) ;
103+ const notOnCurrentPage = [ ...tagResolutionCacheRef . current . values ( ) ] . filter (
104+ ( t ) => t ?. id && ! apiIds . has ( t . id ) ,
105+ ) ;
106+ return [ ...availableTags , ...notOnCurrentPage ] . map ( ( tag ) =>
107+ getTagOption ( tag ) ,
108+ ) ;
109+ } , [ availableTags , loadingTags , tags ] ) ;
94110
95- const selectedTags = useMemo (
96- ( ) => tags . map ( ( tag ) => getTagOption ( tag ) ) ,
97- [ tags ] ,
98- ) ;
111+ const selectedTags = useMemo ( ( ) => {
112+ const resolved = ( tags ?? [ ] ) . filter (
113+ ( tag ) : tag is TagProps => tag != null && tag . id != null ,
114+ ) ;
115+ return resolved . map ( ( tag ) => getTagOption ( tag ) ) ;
116+ } , [ tags ] ) ;
99117
100118 useLinkBuilderKeyboardShortcut ( "t" , ( ) => setIsOpen ( true ) , {
101119 priority : 2 ,
@@ -172,15 +190,17 @@ export const TagSelect = memo(() => {
172190 selected = { selectedTags }
173191 setSelected = { ( newTags ) => {
174192 const selectedIds = newTags . map ( ( { value } ) => value ) ;
175- setValue (
176- "tags" ,
177- selectedIds . map ( ( id ) =>
178- [ ...( availableTags || [ ] ) , ...( tags || [ ] ) ] ?. find (
179- ( t ) => t . id === id ,
180- ) ,
181- ) ,
182- { shouldDirty : true } ,
183- ) ;
193+ const lookup = new Map < string , TagProps > ( ) ;
194+ for ( const t of [ ...( availableTags ?? [ ] ) , ...( tags ?? [ ] ) ] ) {
195+ if ( t ?. id ) lookup . set ( t . id , t ) ;
196+ }
197+ for ( const t of tagResolutionCacheRef . current . values ( ) ) {
198+ if ( t ?. id && ! lookup . has ( t . id ) ) lookup . set ( t . id , t ) ;
199+ }
200+ const nextTags = selectedIds
201+ . map ( ( id ) => lookup . get ( id ) )
202+ . filter ( ( t ) : t is TagProps => t != null ) ;
203+ setValue ( "tags" , nextTags , { shouldDirty : true } ) ;
184204 setSuggestedTags ( ( tags ) =>
185205 tags . filter ( ( { id } ) => ! selectedIds . includes ( id ) ) ,
186206 ) ;
0 commit comments