use a ref for shortcuts

This commit is contained in:
Nate Kelley 2025-09-30 15:30:15 -06:00
parent 7ab328abb5
commit 12b534db2a
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 55 additions and 31 deletions

View File

@ -1,6 +1,6 @@
import type { ListShortcutsResponse, Shortcut } from '@buster/server-shared/shortcuts';
import { useNavigate } from '@tanstack/react-router';
import { useMemo } from 'react';
import { useMemo, useRef } from 'react';
import { useDeleteShortcut, useGetShortcut } from '@/api/buster_rest/shortcuts/queryRequests';
import { ErrorCard } from '@/components/ui/error/ErrorCard';
import { Trash } from '@/components/ui/icons';
@ -16,7 +16,6 @@ import type {
MentionInputSuggestionsProps,
MentionInputSuggestionsRef,
} from '@/components/ui/inputs/MentionInputSuggestions';
import { CircleSpinnerLoader } from '@/components/ui/loaders';
import { ShortcutPopoverContent } from './ShortcutPopoverContent';
export const SHORTCUT_MENTION_TRIGGER = '/';
@ -28,34 +27,42 @@ export const useCreateShortcutsMentionsSuggestions = (
const navigate = useNavigate();
const createShortcutForMention = useCreateShortcutForMention();
const currentItemsRef = useRef(shortcuts);
currentItemsRef.current = shortcuts;
return useMemo(
() =>
createMentionSuggestionExtension({
trigger: SHORTCUT_MENTION_TRIGGER,
items: [
...shortcuts.map(createShortcutForMention),
{ type: 'separator' as const },
{
value: 'manageShortcuts',
label: 'Manage shortcuts',
icon: <PenWriting />,
doNotAddPipeOnSelect: true,
onSelect: () => {
navigate({
to: '/app/home/shortcuts',
});
items: ({ defaultQueryMentionsFilter, query }) => {
const shortcuts = currentItemsRef.current;
const allItems = [
...shortcuts.map(createShortcutForMention),
{ type: 'separator' as const },
{
value: 'manageShortcuts',
label: 'Manage shortcuts',
icon: <PenWriting />,
doNotAddPipeOnSelect: true,
onSelect: () => {
navigate({
to: '/app/home/shortcuts',
});
},
},
},
{
value: 'createShortcut',
label: 'Create shortcut',
icon: <Plus />,
doNotAddPipeOnSelect: true,
onSelect: () => {
setOpenCreateShortcutModal(true);
{
value: 'createShortcut',
label: 'Create shortcut',
icon: <Plus />,
doNotAddPipeOnSelect: true,
onSelect: () => {
setOpenCreateShortcutModal(true);
},
},
},
],
];
return defaultQueryMentionsFilter(query, allItems);
},
popoverContent: ShortcutPopoverContent,
onChangeTransform: (v) => {
const foundShortcut = shortcuts.find((shortcut) => shortcut.name === v.label);
@ -97,8 +104,8 @@ export const useCreateShortcutForMention = () => {
label: 'Delete',
icon: <Trash />,
value: 'delete',
onClick: () => {
deleteShortcut({ id: shortcut.id });
onClick: async () => {
await deleteShortcut({ id: shortcut.id });
},
},
]}

View File

@ -3,7 +3,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
import { useMemo, useRef, useState } from 'react';
import { fn } from 'storybook/test';
import { createMentionSuggestionExtension } from './createMentionSuggestionOption';
import { defaultQueryMentionsFilter } from './defaultQueryMentionsFilter';
import { MentionInput } from './MentionInput';
import type { MentionInputRef, MentionInputTriggerItem } from './MentionInput.types';
@ -329,6 +328,10 @@ export const DynamicItems: Story = {
{ value: 'Initial Item 2', label: 'Initial Item 2' },
]);
const mentionInputRef = useRef<MentionInputRef>(null);
const currentItemsRef = useRef(dynamicItems);
// Always keep the ref updated with the latest items
currentItemsRef.current = dynamicItems;
const addRandomItem = () => {
const randomNames = [
@ -384,11 +387,15 @@ export const DynamicItems: Story = {
setDynamicItems([]);
};
// Create a stable function that always uses the current items from the ref
const dynamicSuggestions = useMemo(
() =>
createMentionSuggestionExtension({
trigger: '!',
items: dynamicItems,
items: ({ query, defaultQueryMentionsFilter }) => {
const latestItems = currentItemsRef.current;
return defaultQueryMentionsFilter(query, latestItems);
},
pillStyling: {
className: () => {
return 'bg-gradient-to-r from-purple-100 to-pink-100 border-purple-300 text-purple-700 hover:from-purple-200 hover:to-pink-200';
@ -403,7 +410,7 @@ export const DynamicItems: Story = {
);
},
}),
[dynamicItems]
[dynamicItems] // Empty dependency array since we're using the ref for fresh data
);
return (

View File

@ -20,14 +20,24 @@ export const createMentionSuggestionExtension = ({
pillStyling,
}: {
trigger: string;
items: MentionInputTriggerItem[] | ((props: { query: string }) => MentionInputTriggerItem[]); //if no function is provided we will use a literal string match
items:
| MentionInputTriggerItem[]
| ((props: {
query: string;
defaultQueryMentionsFilter: typeof defaultQueryMentionsFilter;
}) => MentionInputTriggerItem[]); //if no function is provided we will use a literal string match
popoverContent?: MentionPopoverContentCallback;
pillStyling?: MentionStylePillProps;
onChangeTransform?: MentionSuggestionExtension['onChangeTransform'];
}): MentionSuggestionExtension => ({
char: trigger,
items:
typeof items === 'function' ? items : ({ query }) => defaultQueryMentionsFilter(query, items),
//beware of stale closures here. We should use a ref to get the latest items
typeof items === 'function'
? (props) => {
return items({ ...props, defaultQueryMentionsFilter });
}
: ({ query }) => defaultQueryMentionsFilter(query, items),
render: () => {
let component: ReactRenderer<MentionListImperativeHandle, MentionListProps<string>>;