From 4cc442bdb03590591f8aa1a1bb34b44cd0954929 Mon Sep 17 00:00:00 2001 From: Nick De Villiers Date: Tue, 20 Feb 2024 14:12:32 +0000 Subject: [PATCH] feat: implement useReducer for FileUploadContainer state management (#105) --- .../FileUpload/FileUpload.stories.tsx | 4 +- src/lib/components/FileUpload/FileUpload.tsx | 41 +++------ src/lib/components/FileUpload/hooks.ts | 85 +++++++++++++++++++ 3 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 src/lib/components/FileUpload/hooks.ts diff --git a/src/lib/components/FileUpload/FileUpload.stories.tsx b/src/lib/components/FileUpload/FileUpload.stories.tsx index 6397b9d..61ef1bb 100644 --- a/src/lib/components/FileUpload/FileUpload.stories.tsx +++ b/src/lib/components/FileUpload/FileUpload.stories.tsx @@ -14,9 +14,9 @@ export default meta; export const Example = { args: { error: "", - help: "Max file size is 2GB.", + help: "Max file size is 2MB.", label: "Upload files", maxFiles: 7, - maxSize: 2000000000, + maxSize: 2000000, }, }; diff --git a/src/lib/components/FileUpload/FileUpload.tsx b/src/lib/components/FileUpload/FileUpload.tsx index 6cdfaf5..02dcc1a 100644 --- a/src/lib/components/FileUpload/FileUpload.tsx +++ b/src/lib/components/FileUpload/FileUpload.tsx @@ -1,13 +1,15 @@ -import { ReactNode, useCallback, useId, useState } from "react"; +import { ReactNode, useId } from "react"; import { Button, Icon, Label } from "@canonical/react-components"; import classNames from "classnames"; import { useDropzone, DropzoneOptions, FileRejection } from "react-dropzone"; import "./FileUpload.scss"; +import { useFileUpload } from "./hooks"; + import { ProgressIndicator } from "@/lib/elements"; -type FileUploadFile = File & { percentUploaded?: number }; +export type FileUploadFile = File & { percentUploaded?: number }; export interface FileUploadProps { accept?: DropzoneOptions["accept"]; @@ -19,7 +21,7 @@ export interface FileUploadProps { maxSize?: number; onFileUpload: NonNullable; rejectedFiles: FileRejection[]; - removeFile: (file: File) => void; + removeFile: (file: FileUploadFile) => void; removeRejectedFile: (fileRejection: FileRejection) => void; } @@ -134,35 +136,20 @@ export const FileUploadContainer = ({ FileUploadProps, "accept" | "error" | "help" | "label" | "maxFiles" | "maxSize" >) => { - const [files, setFiles] = useState([]); - const [rejectedFiles, setRejectedFiles] = useState([]); - - const onFileUpload = useCallback( - (acceptedFiles: File[], fileRejections: FileRejection[]) => { - setFiles([...files, ...acceptedFiles]); - setRejectedFiles([...rejectedFiles, ...fileRejections]); - }, - [files, rejectedFiles], - ); - - const removeFile = (file: File) => { - const newFiles = [...files]; - newFiles.splice(newFiles.indexOf(file), 1); - setFiles(newFiles); - }; - - const removeRejectedFile = (fileRejection: FileRejection) => { - const newRejectedFiles = [...rejectedFiles]; - newRejectedFiles.splice(newRejectedFiles.indexOf(fileRejection), 1); - setRejectedFiles(newRejectedFiles); - }; + const { + acceptedFiles, + fileRejections, + onFileUpload, + removeFile, + removeRejectedFile, + } = useFileUpload(); return ( { + const fileUploadReducer = ( + state: FileUploadState, + action: FileUploadActions, + ): FileUploadState => { + switch (action.type) { + case "add-files": + return { + acceptedFiles: [ + ...state.acceptedFiles, + ...action.payload.acceptedFiles, + ], + fileRejections: [ + ...state.fileRejections, + ...action.payload.fileRejections, + ], + }; + case "remove-accepted": { + return { + ...state, + acceptedFiles: state.acceptedFiles.filter( + (file) => file !== action.payload, + ), + }; + } + case "remove-rejected": { + return { + ...state, + fileRejections: state.fileRejections.filter( + (rejection) => rejection !== action.payload, + ), + }; + } + } + }; + + const [{ acceptedFiles, fileRejections }, dispatch] = useReducer( + fileUploadReducer, + { + acceptedFiles: [], + fileRejections: [], + }, + ); + + const onFileUpload = useCallback( + (acceptedFiles: FileUploadFile[], fileRejections: FileRejection[]) => + dispatch({ + type: "add-files", + payload: { acceptedFiles, fileRejections }, + }), + [dispatch], + ); + + const removeFile = (file: FileUploadFile) => + dispatch({ type: "remove-accepted", payload: file }); + + const removeRejectedFile = (fileRejection: FileRejection) => + dispatch({ type: "remove-rejected", payload: fileRejection }); + + return { acceptedFiles, fileRejections, onFileUpload, removeFile, removeRejectedFile }; + +}