From e4e61cfb23e27b6cde019d34a5163718925df513 Mon Sep 17 00:00:00 2001 From: Deepak Jose Date: Fri, 11 Oct 2024 16:36:12 +0530 Subject: [PATCH 1/2] Refactored the useFileUploadStore to nest files under the context id to prevent multiple editors rendering the same upload files status in the same page. --- src/stores/useFileUploadStore.js | 69 +++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/stores/useFileUploadStore.js b/src/stores/useFileUploadStore.js index 7cf45a3c..f0dace9e 100644 --- a/src/stores/useFileUploadStore.js +++ b/src/stores/useFileUploadStore.js @@ -5,41 +5,70 @@ import { create } from "zustand"; /** @type {import("neetocommons/react-utils").ZustandStoreHook} */ const useFileUploadStore = create( withImmutableActions((set, get) => ({ - files: [], - isUploading: false, + // {[contextId]: { files: File[], isUploading: boolean }} - addFiles: newFiles => - set(state => ({ files: [...state.files, ...newFiles] })), + addFiles: (contextId, newFiles) => + set(state => ({ + [contextId]: { + ...state[contextId], + files: [...(state[contextId]?.["files"] || []), ...newFiles], + }, + })), - updateFileStatus: (fileId, status) => + updateFileStatus: (contextId, fileId, status) => set(state => ({ - files: state.files.map(file => - file.id === fileId ? { ...file, status } : file - ), + [contextId]: { + ...state[contextId], + files: state[contextId]?.["files"]?.map(file => + file.id === fileId ? { ...file, status } : file + ), + }, })), - updateFileUploadProgress: (fileId, progress) => + updateFileUploadProgress: (contextId, fileId, progress) => set(state => ({ - files: state.files.map(file => - file.id === fileId ? { ...file, progress } : file - ), + [contextId]: { + ...state[contextId], + files: + state[contextId]?.["files"]?.map(file => + file.id === fileId ? { ...file, progress } : file + ) || [], + }, })), - removeFile: fileId => - set(state => ({ files: removeById(fileId, state.files) })), + removeFile: (contextId, fileId) => + set(state => ({ + [contextId]: { + ...state[contextId], + files: removeById(fileId, state[contextId]?.["files"] || []), + }, + })), - setIsUploading: status => set({ isUploading: status }), + setIsUploading: (contextId, status) => + set(state => ({ + [contextId]: { + ...(state[contextId] ?? []), + isUploading: status, + }, + })), - getNextQueuedFile: () => { - const { files } = get(); + getNextQueuedFile: contextId => { + const { files } = get()[contextId]; return findBy(({ status }) => status === "queued", files); }, - removeFilesFromQueue: uploadedFiles => { - const { files } = get(); + removeFilesFromQueue: (contextId, uploadedFiles) => { + const data = get()[contextId]; - set({ files: files.filter(({ id }) => !uploadedFiles.includes(id)) }); + set({ + [contextId]: { + ...data, + files: (data["files"] || []).filter( + ({ id }) => !uploadedFiles.includes(id) + ), + }, + }); }, })) ); From 6f3907fab6e681e83e028c85ab52634d5a8754b2 Mon Sep 17 00:00:00 2001 From: Deepak Jose Date: Fri, 11 Oct 2024 16:39:34 +0530 Subject: [PATCH 2/2] Refactored the useFileUploader hook to use a context id to identify the running instance. --- src/hooks/useFileUploader.js | 33 ++++++++++++++++++++------------- src/hooks/utils/index.js | 2 ++ 2 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 src/hooks/utils/index.js diff --git a/src/hooks/useFileUploader.js b/src/hooks/useFileUploader.js index 5bf393a4..08739850 100644 --- a/src/hooks/useFileUploader.js +++ b/src/hooks/useFileUploader.js @@ -1,4 +1,7 @@ -// eslint-disable-next-line @bigbinary/neeto/no-axios-import-outside-apis, @bigbinary/neeto/use-snake-case-for-api-connector-filename +// eslint-disable-next-line @bigbinary/neeto/use-snake-case-for-api-connector-filename +import { useRef } from "react"; + +// eslint-disable-next-line @bigbinary/neeto/no-axios-import-outside-apis, import axios from "axios"; import { noop, hyphenate, isNot, isNotPresent } from "neetocist"; import { Toastr } from "neetoui"; @@ -9,6 +12,7 @@ import useFileUploadStore from "src/stores/useFileUploadStore"; import DirectUpload from "utils/DirectUpload"; import { FILE_UPLOAD_STATUS } from "./constants/fileUploader"; +import { getRandomString } from "./utils"; import { selectFiles } from "./utils/fileUploader"; let uploadControllers = {}; @@ -18,26 +22,27 @@ const useFileUploader = ({ attachments: previousAttachments = [], setIsUploadingOnHost = noop, }) => { + const contextIdRef = useRef(getRandomString()); const { t } = useTranslation(); + const { current: contextId } = contextIdRef; const { addFiles: addFilesToStore, - isUploading, getNextQueuedFile, updateFileStatus, removeFilesFromQueue, setIsUploading, updateFileUploadProgress, - files, removeFile, } = useFileUploadStore.pick(); + const { files = [], isUploading } = useFileUploadStore.pick([contextId]); const handleUploadProgress = (xhr, file) => { if (!xhr.event.lengthComputable) return; const bytesUploaded = xhr.loaded; const bytesTotal = xhr.total; const progress = Math.round((bytesUploaded / bytesTotal) * 100); - updateFileUploadProgress(file.id, progress); + updateFileUploadProgress(contextId, file.id, progress); }; const uploadFile = async file => { @@ -51,7 +56,7 @@ const useFileUploader = ({ try { const { data = {}, ...response } = await upload.create(); - updateFileStatus(file.id, FILE_UPLOAD_STATUS.UPLOADED); + updateFileStatus(contextId, file.id, FILE_UPLOAD_STATUS.UPLOADED); return { id: file.id, @@ -66,23 +71,24 @@ const useFileUploader = ({ } // eslint-disable-next-line no-console console.error("Failed to upload attachment", error); - removeFile(file.id); + removeFile(contextId, file.id); return null; } }; const handleUploadFiles = async () => { - const queuedFile = getNextQueuedFile(); + const queuedFile = getNextQueuedFile(contextId); if (isNotPresent(queuedFile)) { - setIsUploading(false); + setIsUploading(contextId, false); setIsUploadingOnHost(false); return []; } - updateFileStatus(queuedFile.id, FILE_UPLOAD_STATUS.UPLOADING); + updateFileStatus(contextId, queuedFile.id, FILE_UPLOAD_STATUS.UPLOADING); + const [uploadedFile, remainingUploadedFiles] = await Promise.all([ uploadFile(queuedFile), handleUploadFiles(), @@ -90,14 +96,14 @@ const useFileUploader = ({ const uploadedFiles = [uploadedFile, ...remainingUploadedFiles]; const uploadedFileIds = pluck("id", uploadedFiles); - removeFilesFromQueue(uploadedFileIds); + removeFilesFromQueue(contextId, uploadedFileIds); uploadControllers = omit(uploadedFileIds, uploadControllers); return uploadedFiles.filter(isNot(null)); }; const uploadFiles = () => { - setIsUploading(true); + setIsUploading(contextId, true); setIsUploadingOnHost(true); return handleUploadFiles(); @@ -117,12 +123,13 @@ const useFileUploader = ({ type: file.type, status: FILE_UPLOAD_STATUS.QUEUED, })); - addFilesToStore(selectedFiles); + + addFilesToStore(contextId, selectedFiles); }; const cancelUpload = fileId => { uploadControllers[fileId]?.abort?.(); - removeFile(fileId); + removeFile(contextId, fileId); }; return { diff --git a/src/hooks/utils/index.js b/src/hooks/utils/index.js new file mode 100644 index 00000000..a3e0a2f7 --- /dev/null +++ b/src/hooks/utils/index.js @@ -0,0 +1,2 @@ +export const getRandomString = () => + Math.random().toString(36).substring(7).toUpperCase();