Skip to content

Commit b650d5d

Browse files
committed
fix: message composer cyclical deps
1 parent 5c30e24 commit b650d5d

File tree

4 files changed

+102
-105
lines changed

4 files changed

+102
-105
lines changed

package/src/contexts/messageComposerContext/MessageComposerAPIContext.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import React, { useContext, useMemo } from 'react';
1+
import React, { useContext } from 'react';
22

33
import { LocalMessage, type MessageComposer } from 'stream-chat';
44

5-
import { useStableCallback } from '../../hooks';
6-
import { useMessageComposer } from '../messageInputContext/hooks/useMessageComposer';
75
import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
86
import { isTestEnvironment } from '../utils/isTestEnvironment';
97

@@ -18,20 +16,12 @@ export const MessageComposerAPIContext = React.createContext(
1816
);
1917

2018
type Props = React.PropsWithChildren<{
21-
value: Pick<MessageComposerAPIContextValue, 'setEditingState' | 'clearEditingState'>;
19+
value: MessageComposerAPIContextValue;
2220
}>;
2321

2422
export const MessageComposerAPIProvider = ({ children, value }: Props) => {
25-
const messageComposer = useMessageComposer();
26-
27-
const setQuotedMessage = useStableCallback((message: LocalMessage | null) =>
28-
messageComposer.setQuotedMessage(message),
29-
);
30-
31-
const contextValue = useMemo(() => ({ setQuotedMessage, ...value }), [setQuotedMessage, value]);
32-
3323
return (
34-
<MessageComposerAPIContext.Provider value={contextValue}>
24+
<MessageComposerAPIContext.Provider value={value}>
3525
{children}
3626
</MessageComposerAPIContext.Provider>
3727
);

package/src/contexts/messageComposerContext/MessageComposerContext.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99

1010
import { ChannelProps } from '../../components';
1111
import { useStableCallback } from '../../hooks';
12+
import { useCreateMessageComposer } from '../messageInputContext/hooks/useCreateMessageComposer';
1213
import { ThreadContextValue } from '../threadContext/ThreadContext';
1314
import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
1415
import { isTestEnvironment } from '../utils/isTestEnvironment';
@@ -47,9 +48,15 @@ export const MessageComposerProvider = ({ children, value }: Props) => {
4748

4849
const messageComposerContextValue = useMemo(() => ({ editing, ...value }), [editing, value]);
4950

51+
const messageComposer = useCreateMessageComposer(messageComposerContextValue);
52+
53+
const setQuotedMessage = useStableCallback((message: LocalMessage | null) =>
54+
messageComposer.setQuotedMessage(message),
55+
);
56+
5057
const messageComposerAPIContextValue = useMemo(
51-
() => ({ clearEditingState, setEditingState }),
52-
[clearEditingState, setEditingState],
58+
() => ({ clearEditingState, setEditingState, setQuotedMessage }),
59+
[clearEditingState, setEditingState, setQuotedMessage],
5360
);
5461

5562
return (
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { useEffect, useMemo } from 'react';
2+
3+
import { FixedSizeQueueCache, MessageComposer } from 'stream-chat';
4+
5+
import { useChatContext } from '../../chatContext/ChatContext';
6+
import { MessageComposerContextValue } from '../../messageComposerContext/MessageComposerContext';
7+
8+
const queueCache = new FixedSizeQueueCache<string, MessageComposer>(64);
9+
10+
export const useCreateMessageComposer = ({
11+
editing: editedMessage,
12+
thread: parentMessage,
13+
threadInstance,
14+
channel,
15+
}: Pick<MessageComposerContextValue, 'channel' | 'threadInstance' | 'thread' | 'editing'>) => {
16+
const { client } = useChatContext();
17+
18+
const cachedEditedMessage = useMemo(() => {
19+
if (!editedMessage) return undefined;
20+
21+
return editedMessage;
22+
// eslint-disable-next-line react-hooks/exhaustive-deps
23+
}, [editedMessage?.id]);
24+
25+
const cachedParentMessage = useMemo(() => {
26+
if (!parentMessage) return undefined;
27+
28+
return parentMessage;
29+
// eslint-disable-next-line react-hooks/exhaustive-deps
30+
}, [parentMessage?.id]);
31+
32+
// composer hierarchy
33+
// edited message (always new) -> thread instance (own) -> thread message (always new) -> channel (own)
34+
// editedMessage ?? thread ?? parentMessage ?? channel;
35+
36+
const messageComposer = useMemo(() => {
37+
if (cachedEditedMessage) {
38+
const tag = MessageComposer.constructTag(cachedEditedMessage);
39+
40+
const cachedComposer = queueCache.get(tag);
41+
if (cachedComposer) return cachedComposer;
42+
43+
return new MessageComposer({
44+
client,
45+
composition: cachedEditedMessage,
46+
compositionContext: cachedEditedMessage,
47+
});
48+
} else if (threadInstance) {
49+
return threadInstance.messageComposer;
50+
} else if (cachedParentMessage) {
51+
const compositionContext = {
52+
...cachedParentMessage,
53+
legacyThreadId: cachedParentMessage.id,
54+
};
55+
56+
const tag = MessageComposer.constructTag(compositionContext);
57+
58+
const cachedComposer = queueCache.get(tag);
59+
if (cachedComposer) return cachedComposer;
60+
61+
// legacy thread will receive new composer
62+
return new MessageComposer({
63+
client,
64+
compositionContext,
65+
});
66+
} else {
67+
return channel.messageComposer;
68+
}
69+
}, [cachedEditedMessage, cachedParentMessage, channel, client, threadInstance]);
70+
71+
if (
72+
(['legacy_thread', 'message'] as MessageComposer['contextType'][]).includes(
73+
messageComposer.contextType,
74+
) &&
75+
!queueCache.peek(messageComposer.tag)
76+
) {
77+
queueCache.add(messageComposer.tag, messageComposer);
78+
}
79+
80+
useEffect(() => {
81+
const unsubscribe = messageComposer.registerSubscriptions();
82+
return () => {
83+
unsubscribe();
84+
};
85+
}, [messageComposer]);
86+
87+
return messageComposer;
88+
};
Lines changed: 2 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,9 @@
1-
import { useEffect, useMemo } from 'react';
1+
import { useCreateMessageComposer } from './useCreateMessageComposer';
22

3-
import { FixedSizeQueueCache, MessageComposer } from 'stream-chat';
4-
5-
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
6-
import {
7-
MessageComposerContextValue,
8-
useMessageComposerContext,
9-
} from '../../messageComposerContext/MessageComposerContext';
10-
11-
const queueCache = new FixedSizeQueueCache<string, MessageComposer>(64);
3+
import { useMessageComposerContext } from '../../messageComposerContext/MessageComposerContext';
124

135
export const useMessageComposer = () => {
146
const messageComposerContext = useMessageComposerContext();
157

168
return useCreateMessageComposer(messageComposerContext);
179
};
18-
19-
export const useCreateMessageComposer = ({
20-
editing: editedMessage,
21-
thread: parentMessage,
22-
threadInstance,
23-
channel,
24-
}: Pick<MessageComposerContextValue, 'channel' | 'threadInstance' | 'thread' | 'editing'>) => {
25-
const { client } = useChatContext();
26-
27-
const cachedEditedMessage = useMemo(() => {
28-
if (!editedMessage) return undefined;
29-
30-
return editedMessage;
31-
// eslint-disable-next-line react-hooks/exhaustive-deps
32-
}, [editedMessage?.id]);
33-
34-
const cachedParentMessage = useMemo(() => {
35-
if (!parentMessage) return undefined;
36-
37-
return parentMessage;
38-
// eslint-disable-next-line react-hooks/exhaustive-deps
39-
}, [parentMessage?.id]);
40-
41-
// composer hierarchy
42-
// edited message (always new) -> thread instance (own) -> thread message (always new) -> channel (own)
43-
// editedMessage ?? thread ?? parentMessage ?? channel;
44-
45-
const messageComposer = useMemo(() => {
46-
if (cachedEditedMessage) {
47-
const tag = MessageComposer.constructTag(cachedEditedMessage);
48-
49-
const cachedComposer = queueCache.get(tag);
50-
if (cachedComposer) return cachedComposer;
51-
52-
return new MessageComposer({
53-
client,
54-
composition: cachedEditedMessage,
55-
compositionContext: cachedEditedMessage,
56-
});
57-
} else if (threadInstance) {
58-
return threadInstance.messageComposer;
59-
} else if (cachedParentMessage) {
60-
const compositionContext = {
61-
...cachedParentMessage,
62-
legacyThreadId: cachedParentMessage.id,
63-
};
64-
65-
const tag = MessageComposer.constructTag(compositionContext);
66-
67-
const cachedComposer = queueCache.get(tag);
68-
if (cachedComposer) return cachedComposer;
69-
70-
// legacy thread will receive new composer
71-
return new MessageComposer({
72-
client,
73-
compositionContext,
74-
});
75-
} else {
76-
return channel.messageComposer;
77-
}
78-
}, [cachedEditedMessage, cachedParentMessage, channel, client, threadInstance]);
79-
80-
if (
81-
(['legacy_thread', 'message'] as MessageComposer['contextType'][]).includes(
82-
messageComposer.contextType,
83-
) &&
84-
!queueCache.peek(messageComposer.tag)
85-
) {
86-
queueCache.add(messageComposer.tag, messageComposer);
87-
}
88-
89-
useEffect(() => {
90-
const unsubscribe = messageComposer.registerSubscriptions();
91-
return () => {
92-
unsubscribe();
93-
};
94-
}, [messageComposer]);
95-
96-
return messageComposer;
97-
};

0 commit comments

Comments
 (0)