Skip to content

Instantly share code, notes, and snippets.

@MrSnor
Created October 28, 2023 03:07
Show Gist options
  • Select an option

  • Save MrSnor/df9b55b4a27a39b483dfb864adab737c to your computer and use it in GitHub Desktop.

Select an option

Save MrSnor/df9b55b4a27a39b483dfb864adab737c to your computer and use it in GitHub Desktop.
The code snippet is a React component that allows users to upload files by dragging and dropping them or selecting them from their device. It displays a container with a message and an icon, and previews the uploaded files with an option to remove them.

React File Upload Component with Previews

Preview:
import { useState } from 'react';
// import './FileUpload.css';
import { useDropzone } from 'react-dropzone';
import { useEffect } from 'react';
import { cn } from '@/lib/utils';
import { Trash } from 'lucide-react';
import { AnimatePresence, motion } from 'framer-motion';

const FileUpload = (props) => {
  const [files, setFiles] = useState([]);

  const { getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } =
    useDropzone({
      accept: { 'image/*': [] },
      onDrop: (acceptedFiles) => {
        setFiles((prevFiles) => {
          const newFiles = acceptedFiles.filter((file) => {
            return !prevFiles.some((prevFile) => prevFile.name === file.name);
          });

          return [
            ...prevFiles,
            ...newFiles.map((file) =>
              Object.assign(file, {
                preview: URL.createObjectURL(file),
              })
            ),
          ];
        });
      },
    });

  return (
    <div className="container group">
      {/* button to reset files list */}
      <button
        type="button"
        className="my-4 ml-auto block text-rose-500 border border-red-500 hover:bg-rose-500 hover:text-white transition-colors p-1 rounded"
        onClick={() => setFiles([])}
      >
        Reset
      </button>
      <div
        className={cn(
          'flex flex-col items-center p-5 py-32 border-2 border-dashed rounded bg-gray-100 text-gray-500 outline-none transition-colors duration-200 ease-in-out cursor-pointer border-gray-400 hover:border-gray-700 group-hover:text-gray-700',
          {
            'border-blue-400': isFocused,
          },
          {
            'border-green-400': isDragAccept,
          },
          {
            'border-red-400': isDragReject,
          }
        )}
        {...getRootProps()}
      >
        <input {...getInputProps()} />
        <svg
          className="h-12 w-12"
          fill="none"
          height="24"
          stroke="currentColor"
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeWidth="2"
          viewBox="0 0 24 24"
          width="24"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
          <path d="M12 12v9" />
          <path d="m16 16-4-4-4 4" />
        </svg>
        <p>Drag 'n' drop some files here, or click to select files</p>
      </div>
      <Previews files={files} setFiles={setFiles} />{' '}
      {/* Use the Previews component */}
    </div>
  );
};

export default FileUpload;

function Previews({ files, setFiles }) {
  // animation variants
  const dropIn = {
    hidden: {
      y: '-100vh',
      opacity: 0,
    },
    visible: {
      y: '0',
      opacity: 1,
      transition: {
        duration: 0.1,
        type: 'spring',
        damping: 25,
        stiffness: 500,
      },
    },
    exit: {
      y: '100vh',
      opacity: 0,
    },
  };

  const scaleUp = {
    hidden: {
      opacity: 0,
      scale: 0,
    },
    visible: {
      opacity: 1,
      scale: 1,
      transition: {
        duration: 0.2,
        ease: 'easeIn',
      },
    },
    exit: {
      opacity: 0,
      scale: 0,
      transition: {
        duration: 0.15,
        ease: 'easeOut',
      },
    },
  };

  const { getRootProps, getInputProps } = useDropzone({
    accept: { 'image/*': [] },
    onDrop: (acceptedFiles) => {
      const updatedFiles = [
        ...files,
        ...acceptedFiles.map((file) =>
          Object.assign(file, {
            preview: URL.createObjectURL(file),
          })
        ),
      ];
      setFiles(updatedFiles);
    },
  });

  useEffect(() => {
    return () => files.forEach((file) => URL.revokeObjectURL(file.preview));
  }, [files]);

  const thumbs = files.map((file) => (
    <motion.div
      className={cn(
        'inline-flex flex-col items-center justify-center rounded-md border border-gray-300 mb-2 mr-2  px-4 py-8 box-border relative'
      )}
      key={file.name}
      initial="hidden"
      animate="visible"
      exit="exit"
      variants={dropIn}
    >
      <div className={cn('flex min-w-0 overflow-hidden')}>
        <img
          src={file.preview}
          className={cn('block w-auto h-full')}
          onLoad={() => {
            URL.revokeObjectURL(file.preview);
          }}
        />
      </div>
      <button
        className={cn(
          'bg-red-600 text-white hover:text-red-600 hover:bg-white border hover:border-red-600 rounded-bl-md px-1 absolute -top-0 -right-0 aspect-square transition-colors'
        )}
        onClick={() => {
          // Remove the file from the 'files' array
          const updatedFiles = files.filter((f) => f !== file);
          setFiles(updatedFiles);
        }}
      >
        <Trash className="w-4 aspect-square" />
        {/* <svg
          className=" w-4 h-4 text-white"
          fill="none"
          height="24"
          stroke="currentColor"
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeWidth="2"
          viewBox="0 0 24 24"
          width="24"
          xmlns="http://www.w3.org/2000/svg"
        >
          <rect height="18" rx="2" ry="2" width="18" x="3" y="3" />
          <line x1="3" x2="21" y1="9" y2="9" />
          <path d="m9 16 3-3 3 3" />
        </svg>
        <span className="sr-only">Remove File</span> */}
      </button>
    </motion.div>
  ));

  return (
    <aside className={cn('grid grid-cols-6 py-2')}>
      <AnimatePresence initial={false} mode="sync">
        {thumbs}
      </AnimatePresence>
    </aside>
  );
}
Associated Context
Type Code Snippet ( .jsx )
Associated Tags React useDropzone Drag and Drop React useEffect Image Preview Drag/Drop Trash Component AnimatePresence Motion reactjs Use Effect Remove File CSS Styling File Upload Image URL Animation Variants React Hooks Framer Motion Framework: React GitHub: vite-project Object URL React Dropzone React useState FileUpload.jsx
Associated Commit Messages add fileUpload component
Update style of FileUpload

changed layout of previews to grid
modify fileUpload to have remove button, and to not accept same files again
add animation to fileUpload component, using framer motion
Add a reset button to remove all files from fileUpload
💡 Smart Description This code creates a button that allows the user to upload files from an image, and displays it in a container. It also includes styles for displaying file names or images
The code snippet is a React component that allows users to upload files by dragging and dropping them or selecting them from their device. It displays a container with a message and an icon, and previews the uploaded files with an option to remove them.
🔎 Suggested Searches React file upload button
React useState and useDropzone for FileUpload
Framer-motion action eact
React dropzone with Image/*' in files list
How to create an image URL eact
React FileUpload component with drag and drop
React useState and useEffect in FileUpload component
React useDropzone hook in FileUpload component
React Previews component in FileUpload
React AnimatePresence and motion in FileUpload component
Related Links https://tailwindcss.com/docs/border-color
http://www.w3.org/2000/svg
https://www.w3schools.com/jsref/met_document_createelement.asp
https://github.com/MrSnor/vite-project/commits
https://github.com/MrSnor/vite-project
https://tailwindcss.com/docs/transition-property
https://www.npmjs.com/package/cn
https://www.npmjs.com/package/react-dropzone
https://github.com/MrSnor/vite-project/blob/3a98ffdaf59bfac9c484bc14a61e1fab10fd1c4b/src/components/FileUploadAndPrev/FileUpload.jsx
https://react-dropzone.js.org/
https://lucide.dev/
Related People No Related People
Sensitive Information No Sensitive Information Detected
Shareable Link https://mrsnor.pieces.cloud/?p=ca07419713
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment