Skip to content

Commit 46c9add

Browse files
refactor: simplify WithDragAndDropUpload API (#2691)
### 🎯 Goal Yesterday evening I reconsidered how the wrapper should work - this way it's much more simpler and allows multiple `MessageInput` components to work under one wrapper without any clashing. Since the previous feature has not been released yet, we can change the context API without any worry and thus `refactor`.
1 parent d0c32e3 commit 46c9add

File tree

2 files changed

+28
-32
lines changed

2 files changed

+28
-32
lines changed

src/components/MessageInput/MessageInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import type {
2626
} from '../../types/types';
2727
import type { URLEnrichmentConfig } from './hooks/useLinkPreviews';
2828
import type { CustomAudioRecordingConfig } from '../MediaRecorder';
29-
import { useHandleDragAndDropQueuedFiles } from './WithDragAndDropUpload';
29+
import { useRegisterDropHandlers } from './WithDragAndDropUpload';
3030

3131
export type EmojiSearchIndexResult = {
3232
id: string;
@@ -153,7 +153,7 @@ const MessageInputProvider = <
153153
});
154154

155155
// @ts-expect-error generics to be removed
156-
useHandleDragAndDropQueuedFiles(messageInputContextValue);
156+
useRegisterDropHandlers(messageInputContextValue);
157157

158158
return (
159159
<MessageInputContextProvider<StreamChatGenerics, V> value={messageInputContextValue}>

src/components/MessageInput/WithDragAndDropUpload.tsx

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import React, {
66
useContext,
77
useEffect,
88
useMemo,
9-
useState,
9+
useRef,
1010
} from 'react';
1111
import {
1212
MessageInputContextValue,
@@ -18,34 +18,27 @@ import { useDropzone } from 'react-dropzone';
1818
import clsx from 'clsx';
1919

2020
const DragAndDropUploadContext = React.createContext<{
21-
fileQueue: File[];
22-
addFilesToQueue: ((files: File[]) => void) | null;
23-
removeFilesFromQueue: ((files: File[]) => void) | null;
21+
subscribeToDrop: ((fn: (files: File[]) => void) => () => void) | null;
2422
}>({
25-
addFilesToQueue: null,
26-
fileQueue: [],
27-
removeFilesFromQueue: null,
23+
subscribeToDrop: null,
2824
});
2925

3026
export const useDragAndDropUploadContext = () => useContext(DragAndDropUploadContext);
3127

3228
/**
33-
* @private To maintain top -> bottom data flow, the drag-and-drop functionality allows dragging any files to the queue - the closest
34-
* `MessageInputProvider` will be notified through `DragAndDropUploadContext.fileQueue` and starts the upload with `uploadNewAttachments`,
35-
* forwarded files are removed from the queue immediately after.
29+
* @private This hook should be used only once directly in the `MessageInputProvider` to
30+
* register `uploadNewFiles` functions of the rendered `MessageInputs`. Each `MessageInput`
31+
* will then be notified when the drop event occurs from within the `WithDragAndDropUpload`
32+
* component.
3633
*/
37-
export const useHandleDragAndDropQueuedFiles = ({
38-
uploadNewFiles,
39-
}: MessageInputContextValue) => {
40-
const { fileQueue, removeFilesFromQueue } = useDragAndDropUploadContext();
34+
export const useRegisterDropHandlers = ({ uploadNewFiles }: MessageInputContextValue) => {
35+
const { subscribeToDrop } = useDragAndDropUploadContext();
4136

4237
useEffect(() => {
43-
if (!removeFilesFromQueue) return;
38+
const unsubscribe = subscribeToDrop?.(uploadNewFiles);
4439

45-
uploadNewFiles(fileQueue);
46-
47-
removeFilesFromQueue(fileQueue);
48-
}, [fileQueue, removeFilesFromQueue, uploadNewFiles]);
40+
return unsubscribe;
41+
}, [subscribeToDrop, uploadNewFiles]);
4942
};
5043

5144
/**
@@ -78,7 +71,7 @@ export const WithDragAndDropUpload = ({
7871
className?: string;
7972
style?: CSSProperties;
8073
}>) => {
81-
const [files, setFiles] = useState<File[]>([]);
74+
const dropHandlersRef = useRef<Set<(f: File[]) => void>>(new Set());
8275
const { acceptedFiles = [], multipleUploads } = useChannelStateContext();
8376
const { t } = useTranslationContext();
8477

@@ -98,13 +91,16 @@ export const WithDragAndDropUpload = ({
9891
[acceptedFiles],
9992
);
10093

101-
const addFilesToQueue = useCallback((files: File[]) => {
102-
setFiles((cv) => cv.concat(files));
94+
const subscribeToDrop = useCallback((fn: (files: File[]) => void) => {
95+
dropHandlersRef.current.add(fn);
96+
97+
return () => {
98+
dropHandlersRef.current.delete(fn);
99+
};
103100
}, []);
104101

105-
const removeFilesFromQueue = useCallback((files: File[]) => {
106-
if (!files.length) return;
107-
setFiles((cv) => cv.filter((f) => files.indexOf(f) === -1));
102+
const handleDrop = useCallback((files: File[]) => {
103+
dropHandlersRef.current.forEach((fn) => fn(files));
108104
}, []);
109105

110106
const { getRootProps, isDragActive, isDragReject } = useDropzone({
@@ -116,20 +112,20 @@ export const WithDragAndDropUpload = ({
116112
: false,
117113
multiple: multipleUploads,
118114
noClick: true,
119-
onDrop: isWithinMessageInputContext
120-
? messageInputContext.uploadNewFiles
121-
: addFilesToQueue,
115+
onDrop: isWithinMessageInputContext ? messageInputContext.uploadNewFiles : handleDrop,
122116
});
123117

124118
// nested WithDragAndDropUpload components render wrappers without functionality
125119
// (MessageInputFlat has a default WithDragAndDropUpload)
126-
if (dragAndDropUploadContext.removeFilesFromQueue !== null) {
120+
if (dragAndDropUploadContext.subscribeToDrop !== null) {
127121
return <Component className={className}>{children}</Component>;
128122
}
129123

130124
return (
131125
<DragAndDropUploadContext.Provider
132-
value={{ addFilesToQueue, fileQueue: files, removeFilesFromQueue }}
126+
value={{
127+
subscribeToDrop,
128+
}}
133129
>
134130
<Component {...getRootProps({ className, style })}>
135131
{/* TODO: could be a replaceable component */}

0 commit comments

Comments
 (0)