Skip to content

Commit f7e3213

Browse files
committed
fix: improve draft manager
1 parent bb887dd commit f7e3213

File tree

2 files changed

+150
-34
lines changed

2 files changed

+150
-34
lines changed

examples/SampleApp/src/components/DraftsList.tsx

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
1+
import { ActivityIndicator, FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
22
import { DraftsIcon } from '../icons/DraftIcon';
33
import {
44
FileTypes,
@@ -10,10 +10,10 @@ import {
1010
useTranslationContext,
1111
} from 'stream-chat-react-native';
1212
import { DraftManagerState, DraftsManager } from '../utils/DraftsManager';
13-
import { useEffect, useMemo } from 'react';
13+
import { useCallback, useEffect, useMemo } from 'react';
1414
import dayjs from 'dayjs';
15-
import { useNavigation } from '@react-navigation/native';
16-
import { ChannelResponse, DraftMessage, MessageResponseBase } from 'stream-chat';
15+
import { useIsFocused, useNavigation } from '@react-navigation/native';
16+
import { ChannelResponse, DraftMessage, DraftResponse, MessageResponseBase } from 'stream-chat';
1717

1818
export type DraftItemProps = {
1919
type?: 'channel' | 'thread';
@@ -145,45 +145,66 @@ const selector = (nextValue: DraftManagerState) =>
145145
drafts: nextValue.drafts,
146146
}) as const;
147147

148+
const renderItem = ({ item }: { item: DraftResponse }) => (
149+
<DraftItem
150+
channel={item.channel}
151+
type={item.parent_id ? 'thread' : 'channel'}
152+
date={item.created_at}
153+
message={item.message}
154+
thread={item.parent_message}
155+
parentId={item.parent_id}
156+
/>
157+
);
158+
159+
const renderEmptyComponent = () => (
160+
<Text style={{ textAlign: 'center', padding: 20 }}>No drafts available</Text>
161+
);
162+
148163
export const DraftsList = () => {
164+
const isFocused = useIsFocused();
149165
const { client } = useChatContext();
150166
const draftsManager = useMemo(() => new DraftsManager({ client }), [client]);
151167

152168
useEffect(() => {
153-
draftsManager.reload({ force: true });
169+
if (isFocused) {
170+
draftsManager.activate();
171+
} else {
172+
draftsManager.deactivate();
173+
}
174+
}, [draftsManager, isFocused]);
175+
176+
useEffect(() => {
177+
draftsManager.registerSubscriptions();
178+
179+
return () => {
180+
draftsManager.deactivate();
181+
draftsManager.unregisterSubscriptions();
182+
};
154183
}, [draftsManager]);
155184

156185
const { isLoading, drafts, isLoadingNext } = useStateStore(draftsManager.state, selector);
157186

187+
const onRefresh = useCallback(() => {
188+
draftsManager.reload({ force: true });
189+
}, [draftsManager]);
190+
191+
const onEndReached = useCallback(() => {
192+
if (!isLoadingNext) {
193+
draftsManager.loadNextPage();
194+
}
195+
}, [draftsManager, isLoadingNext]);
196+
158197
return (
159-
<View>
160-
<FlatList
161-
data={drafts}
162-
refreshing={isLoading}
163-
keyExtractor={(item) => item.message.id}
164-
renderItem={({ item }) => (
165-
<DraftItem
166-
channel={item.channel}
167-
type={item.parent_id ? 'thread' : 'channel'}
168-
date={item.created_at}
169-
message={item.message}
170-
thread={item.parent_message}
171-
parentId={item.parent_id}
172-
/>
173-
)}
174-
onRefresh={() => draftsManager.reload({ force: true })}
175-
ListEmptyComponent={
176-
!isLoading && drafts.length === 0 ? (
177-
<Text style={{ textAlign: 'center', padding: 20 }}>No drafts available</Text>
178-
) : null
179-
}
180-
onEndReached={() => {
181-
if (!isLoadingNext) {
182-
draftsManager.loadNextPage();
183-
}
184-
}}
185-
/>
186-
</View>
198+
<FlatList
199+
contentContainerStyle={{ flexGrow: 1 }}
200+
data={drafts}
201+
refreshing={isLoading}
202+
keyExtractor={(item) => item.message.id}
203+
renderItem={renderItem}
204+
onRefresh={onRefresh}
205+
ListEmptyComponent={renderEmptyComponent}
206+
onEndReached={onEndReached}
207+
/>
187208
);
188209
};
189210

examples/SampleApp/src/utils/DraftsManager.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DraftFilters, DraftResponse, DraftSort, Pager, StateStore, StreamChat } from 'stream-chat';
2+
import { WithSubscriptions } from './WithSubscription';
23

34
export type QueryDraftOptions = Pager & {
45
filter?: DraftFilters;
@@ -34,7 +35,7 @@ export type DraftManagerPagination = {
3435
nextCursor: string | null;
3536
};
3637

37-
export class DraftsManager {
38+
export class DraftsManager extends WithSubscriptions {
3839
public readonly state: StateStore<DraftManagerState>;
3940
private client: StreamChat;
4041
private draftsByIdGetterCache: {
@@ -43,6 +44,7 @@ export class DraftsManager {
4344
};
4445

4546
constructor({ client }: { client: StreamChat }) {
47+
super();
4648
this.client = client;
4749
this.state = new StateStore<DraftManagerState>(DRAFT_MANAGER_INITIAL_STATE);
4850
this.draftsByIdGetterCache = { drafts: [], draftsById: {} };
@@ -78,6 +80,99 @@ export class DraftsManager {
7880
this.state.partialNext({ active: false });
7981
};
8082

83+
private subscribeDraftUpdated = () =>
84+
this.client.on('draft.updated', (event) => {
85+
if (!event.draft) {
86+
return;
87+
}
88+
89+
const draftData = event.draft;
90+
91+
const { drafts } = this.state.getLatestValue();
92+
93+
const newDrafts = [...drafts];
94+
95+
let existingDraftIndex = -1;
96+
97+
if (draftData.parent_id) {
98+
existingDraftIndex = drafts.findIndex(
99+
(draft) =>
100+
draft.parent_id === draftData.parent_id &&
101+
draft.channel?.cid === draftData.channel?.cid,
102+
);
103+
} else {
104+
existingDraftIndex = drafts.findIndex(
105+
(draft) => draft.channel?.cid === draftData.channel?.cid,
106+
);
107+
}
108+
109+
if (existingDraftIndex !== -1) {
110+
newDrafts[existingDraftIndex] = draftData;
111+
} else {
112+
newDrafts.push(draftData);
113+
}
114+
115+
this.state.partialNext({ drafts: newDrafts });
116+
}).unsubscribe;
117+
118+
private subscribeDraftDeleted = () =>
119+
this.client.on('draft.deleted', (event) => {
120+
if (!event.draft) {
121+
return;
122+
}
123+
124+
const { drafts } = this.state.getLatestValue();
125+
126+
const newDrafts = [...drafts];
127+
128+
const draftData = event.draft;
129+
130+
let existingDraftIndex = -1;
131+
132+
if (draftData.parent_id) {
133+
existingDraftIndex = drafts.findIndex(
134+
(draft) =>
135+
draft.parent_id === draftData.parent_id &&
136+
draft.channel?.cid === draftData.channel?.cid,
137+
);
138+
} else {
139+
existingDraftIndex = drafts.findIndex(
140+
(draft) => draft.channel?.cid === draftData.channel?.cid,
141+
);
142+
}
143+
144+
if (existingDraftIndex !== -1) {
145+
newDrafts.splice(existingDraftIndex, 1);
146+
}
147+
148+
this.state.partialNext({
149+
drafts: newDrafts,
150+
});
151+
}).unsubscribe;
152+
153+
private subscribeReloadOnActivation = () =>
154+
this.state.subscribeWithSelector(
155+
(nextValue) => ({ active: nextValue.active }),
156+
({ active }) => {
157+
if (active) {
158+
this.reload();
159+
}
160+
},
161+
);
162+
163+
public registerSubscriptions = () => {
164+
if (this.hasSubscriptions) {
165+
return;
166+
}
167+
this.addUnsubscribeFunction(this.subscribeReloadOnActivation());
168+
this.addUnsubscribeFunction(this.subscribeDraftUpdated());
169+
this.addUnsubscribeFunction(this.subscribeDraftDeleted());
170+
};
171+
172+
public unregisterSubscriptions = () => {
173+
return super.unregisterSubscriptions();
174+
};
175+
81176
public reload = async ({ force = false } = {}) => {
82177
const { drafts, isDraftsOrderStale, pagination, ready } = this.state.getLatestValue();
83178
if (pagination.isLoading) {

0 commit comments

Comments
 (0)