Skip to content

Commit b5bdc73

Browse files
MartinCupelaclaude
andcommitted
chore(demo): define the inline-edit action at module level
The "Edit inline" action and the `MessageActions` wrapper that added it were component types created inside `useMemo`, so a change to one of the memo's dependencies gave React a new type and remounted the actions menu instead of re-rendering it. `InlineEditMessageAction` is now a module-level component, and `ConfigurableMessageActions` adds it next to the built-in edit, alongside the demo's other custom actions, while the setting is on. It starts editing its row through `InlineEditContext`, which `InlineEditableMessage` provides. The `WithComponents` override is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c60bb5e commit b5bdc73

3 files changed

Lines changed: 67 additions & 66 deletions

File tree

‎examples/vite/src/CustomMessageActions/ConfigurableMessageActions.tsx‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434

3535
import { useAppSettingsSelector } from '../AppSettings';
3636
import type { MessageActionSurface } from '../AppSettings';
37+
import { InlineEditMessageAction } from '../InlineEditMessage';
3738
import {
3839
MessageInfoPromptDialog,
3940
messageInfoPromptDialogId,
@@ -286,7 +287,11 @@ const CustomViewMessageInfoAction = () => {
286287
);
287288
};
288289

289-
type SupportedCustomMessageActionType = 'delete' | 'markOwnUnread' | 'viewMessageInfo';
290+
type SupportedCustomMessageActionType =
291+
| 'delete'
292+
| 'editInline'
293+
| 'markOwnUnread'
294+
| 'viewMessageInfo';
290295

291296
type CustomMessageActionOverrideSpec = {
292297
actionSetItem: MessageActionSetItem;
@@ -379,6 +384,16 @@ export const ConfigurableMessageActions = (
379384
},
380385
mode: 'replace',
381386
},
387+
// Next to the built-in edit, so the two ways of editing read as alternatives.
388+
editInline: {
389+
actionSetItem: {
390+
Component: InlineEditMessageAction,
391+
placement: 'dropdown',
392+
type: 'editInline',
393+
},
394+
insertBeforeType: 'edit',
395+
mode: 'append',
396+
},
382397
markOwnUnread: {
383398
actionSetItem: {
384399
Component: CustomMarkOwnUnreadMessageAction,
@@ -404,6 +419,10 @@ export const ConfigurableMessageActions = (
404419
...actionOverrides.delete,
405420
enabled: customDeleteEnabled,
406421
},
422+
{
423+
...actionOverrides.editInline,
424+
enabled: surfaceActions.inlineEdit,
425+
},
407426
{
408427
...actionOverrides.markOwnUnread,
409428
enabled: surfaceActions.markOwnUnread,
@@ -430,6 +449,7 @@ export const ConfigurableMessageActions = (
430449
}, [
431450
customDeleteEnabled,
432451
surfaceActions.disabledActionTypes,
452+
surfaceActions.inlineEdit,
433453
surfaceActions.markOwnUnread,
434454
surfaceActions.viewMessageInfo,
435455
props.messageActionSet,
Lines changed: 45 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,68 @@
1-
import { type ComponentProps, useCallback, useEffect, useMemo, useState } from 'react';
1+
import {
2+
createContext,
3+
useCallback,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
} from 'react';
29
import { MessageComposer as MessageComposerController } from 'stream-chat';
310
import type { LocalMessage, MessageComposerState } from 'stream-chat';
411
import {
512
asDynamicKey,
613
ContextMenuButton,
7-
defaultMessageActionSet,
814
MessageUI as DefaultMessageUI,
915
IconEdit,
10-
MessageActions,
11-
type MessageActionSetItem,
1216
MessageComposer,
1317
MessageComposerControllerProvider,
1418
type MessageUIComponentProps,
1519
useChatContext,
16-
useComponentContext,
1720
useContextMenuContext,
1821
useMessageContext,
1922
useStateStore,
2023
useThreadContext,
2124
useTranslationContext,
22-
WithComponents,
2325
} from 'stream-chat-react';
2426

2527
import { useAppSettingsSelector } from '../AppSettings';
2628
import type { MessageActionSurface } from '../AppSettings';
2729

28-
// Next to the built-in edit, so the two ways of editing read as alternatives.
29-
const insertBeforeEdit = (
30-
actionSet: MessageActionSetItem[],
31-
actionSetItem: MessageActionSetItem,
32-
) => {
33-
const editIndex = actionSet.findIndex((item) => 'type' in item && item.type === 'edit');
30+
type InlineEditContextValue = {
31+
startEditing: (message: LocalMessage) => void;
32+
};
3433

35-
if (editIndex < 0) return [...actionSet, actionSetItem];
34+
// Lets the "Edit inline" action, rendered deep in the message's actions menu, start editing the
35+
// row it belongs to.
36+
const InlineEditContext = createContext<InlineEditContextValue | undefined>(undefined);
3637

37-
return [...actionSet.slice(0, editIndex), actionSetItem, ...actionSet.slice(editIndex)];
38+
/**
39+
* The "Edit inline" message action. `ConfigurableMessageActions` adds it to the action set when
40+
* the setting is on; it only works inside an `InlineEditableMessage`, which provides the context.
41+
*/
42+
export const InlineEditMessageAction = () => {
43+
const inlineEdit = useContext(InlineEditContext);
44+
const { closeMenu } = useContextMenuContext();
45+
const { message } = useMessageContext();
46+
const { t } = useTranslationContext();
47+
48+
if (!inlineEdit) return null;
49+
50+
return (
51+
<ContextMenuButton
52+
aria-label={t(
53+
asDynamicKey('viteExample.inlineEdit.action.ariaLabel'),
54+
'Edit message inline',
55+
)}
56+
className='str-chat__message-actions-list-item-button'
57+
Icon={IconEdit}
58+
onClick={() => {
59+
inlineEdit.startEditing(message);
60+
closeMenu();
61+
}}
62+
>
63+
{t(asDynamicKey('viteExample.inlineEdit.action.label'), 'Edit inline')}
64+
</ContextMenuButton>
65+
);
3866
};
3967

4068
const editingSelector = (state: MessageComposerState) => ({
@@ -55,7 +83,6 @@ export const InlineEditableMessage = (props: MessageUIComponentProps) => {
5583
(state) => state.messageActions,
5684
);
5785
const inlineEditEnabled = customMessageActions[surface].inlineEdit;
58-
const { MessageActions: OuterMessageActions = MessageActions } = useComponentContext();
5986

6087
// Only looks - a row nobody edits creates nothing. A composer found here holding an edit is one
6188
// left unfinished while this row was unmounted, so the editor comes straight back.
@@ -92,53 +119,7 @@ export const InlineEditableMessage = (props: MessageUIComponentProps) => {
92119
if (!inlineEditEnabled && editing) editingComposer?.clear();
93120
}, [editing, editingComposer, inlineEditEnabled]);
94121

95-
const MessageActionsWithInlineEdit = useMemo(() => {
96-
const InlineEditAction = () => {
97-
const { closeMenu } = useContextMenuContext();
98-
const { message } = useMessageContext();
99-
const { t } = useTranslationContext();
100-
101-
return (
102-
<ContextMenuButton
103-
aria-label={t(
104-
asDynamicKey('viteExample.inlineEdit.action.ariaLabel'),
105-
'Edit message inline',
106-
)}
107-
className='str-chat__message-actions-list-item-button'
108-
Icon={IconEdit}
109-
onClick={() => {
110-
startEditing(message);
111-
closeMenu();
112-
}}
113-
>
114-
{t(asDynamicKey('viteExample.inlineEdit.action.label'), 'Edit inline')}
115-
</ContextMenuButton>
116-
);
117-
};
118-
119-
const inlineEditActionSetItem: MessageActionSetItem = {
120-
Component: InlineEditAction,
121-
placement: 'dropdown',
122-
type: 'editInline',
123-
};
124-
125-
const Component = (actionsProps: ComponentProps<typeof MessageActions>) => {
126-
const messageActionSet = useMemo(
127-
() =>
128-
insertBeforeEdit(
129-
actionsProps.messageActionSet ?? defaultMessageActionSet,
130-
inlineEditActionSetItem,
131-
),
132-
[actionsProps.messageActionSet],
133-
);
134-
135-
return (
136-
<OuterMessageActions {...actionsProps} messageActionSet={messageActionSet} />
137-
);
138-
};
139-
Component.displayName = 'MessageActionsWithInlineEdit';
140-
return Component;
141-
}, [OuterMessageActions, startEditing]);
122+
const inlineEditContextValue = useMemo(() => ({ startEditing }), [startEditing]);
142123

143124
if (!inlineEditEnabled) return <DefaultMessageUI {...props} />;
144125

@@ -157,8 +138,8 @@ export const InlineEditableMessage = (props: MessageUIComponentProps) => {
157138
}
158139

159140
return (
160-
<WithComponents overrides={{ MessageActions: MessageActionsWithInlineEdit }}>
141+
<InlineEditContext.Provider value={inlineEditContextValue}>
161142
<DefaultMessageUI {...props} />
162-
</WithComponents>
143+
</InlineEditContext.Provider>
163144
);
164145
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { InlineEditableMessage } from './InlineEditMessage';
1+
export { InlineEditableMessage, InlineEditMessageAction } from './InlineEditMessage';

0 commit comments

Comments
 (0)