How to handle pasting images to inputs in React

How to handle pasting images to inputs in React

What happens when you paste

When a user pastes content with Ctrl+V or Command+V, React fires a ClipboardEvent. This event contains a clipboardData property that gives you access to everything that was copied.

The clipboardData object has these main properties:

  • items: A collection of everything in the clipboard

  • files: Any files present in the clipboard

  • types: Format strings like "text/plain" or "image/png"

Clipboard data structure

Each item in the clipboard has:

  • A kind property: Either "string" or "file"

  • A type property: The MIME type such as "image/png" or "text/plain"

More complete example with file handling

This example handles multiple image pastes and shows file information:

import React, { useState, useEffect, useRef } from "react";

interface PastedFile {
  url: string;
  name: string;
  type: string;
  size: number;
  file: File;
}

const ImagePasteInput: React.FC = () => {
  const [pastedFiles, setPastedFiles] = useState<PastedFile[]>([]);
  const [inputValue, setInputValue] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);

  // Set up paste event listener on the document
  useEffect(() => {
    const handlePaste = (event: ClipboardEvent) => {
      // Check if our input element is focused
      if (document.activeElement !== inputRef.current) return;

      const items = event.clipboardData?.items;
      if (!items) return;

      // Process image files
      const newFiles: PastedFile[] = [];

      Array.from(items).forEach((item) => {
        if (item.kind === "file" && item.type.startsWith("image/")) {
          const file = item.getAsFile();

          if (file) {
            newFiles.push({
              url: URL.createObjectURL(file),
              name: file.name || `pasted-image-${Date.now()}.png`,
              type: file.type,
              size: file.size,
              file: file,
            });
          }
        }
      });

      if (newFiles.length > 0) {
        setPastedFiles((prev) => [...prev, ...newFiles]);
        event.preventDefault(); // Prevent pasting text into input
      }
    };

    document.addEventListener("paste", handlePaste);
    return () => {
      document.removeEventListener("paste", handlePaste);
    };
  }, []);

  // Clean up object URLs on unmount
  useEffect(() => {
    return () => {
      pastedFiles.forEach((file) => URL.revokeObjectURL(file.url));
    };
  }, [pastedFiles]);

  const handleRemoveFile = (index: number) => {
    setPastedFiles((prev) => {
      const newFiles = [...prev];
      URL.revokeObjectURL(newFiles[index].url);
      newFiles.splice(index, 1);
      return newFiles;
    });
  };

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value);
  };

  const handleUpload = () => {
    // Example function to handle the upload of pasted files
    console.log(
      "Files to upload:",
      pastedFiles.map((file) => file.file)
    );
    // Here you would typically send these files to your server
  };

  return (
    <div ref={containerRef}>
      <div style={{ marginBottom: "20px" }}>
        <label htmlFor="pasteInput">
          Paste images here (click and press Ctrl+V):
        </label>
        <input
          ref={inputRef}
          id="pasteInput"
          type="text"
          value={inputValue}
          onChange={handleInputChange}
          placeholder="Click here and paste images with Ctrl+V"
          style={{
            display: "block",
            width: "100%",
            padding: "8px",
            border: "1px solid #ccc",
            borderRadius: "4px",
            marginTop: "5px",
          }}
        />
      </div>

      {pastedFiles.length > 0 && (
        <div>
          <h3>Pasted Images:</h3>
          <div style={{ display: "flex", flexWrap: "wrap", gap: "10px" }}>
            {pastedFiles.map((file, index) => (
              <div
                key={index}
                style={{
                  border: "1px solid #eee",
                  padding: "10px",
                  borderRadius: "4px",
                }}
              >
                <img
                  src={file.url}
                  alt={file.name}
                  style={{ maxWidth: "200px", maxHeight: "200px" }}
                />
                <div>Type: {file.type}</div>
                <div>Size: {Math.round(file.size / 1024)} KB</div>
                <button
                  onClick={() => handleRemoveFile(index)}
                  style={{ marginTop: "5px", padding: "4px 8px" }}
                >
                  Remove
                </button>
              </div>
            ))}
          </div>

          <button
            onClick={handleUpload}
            style={{
              marginTop: "20px",
              padding: "8px 16px",
              backgroundColor: "#4285f4",
              color: "white",
              border: "none",
              borderRadius: "4px",
              cursor: "pointer",
            }}
          >
            Upload Images
          </button>
        </div>
      )}
    </div>
  );
};

export default ImagePasteInput;

Things to remember

The clipboard can contain different types of content:

  • Plain text

  • Rich text or HTML

  • Images from screenshots or copied content

  • Files from explorer/finder

  • Mixed content with multiple formats

When someone pastes an image:

  • It appears as a file-like object in the clipboard

  • It has a MIME type like "image/png" or "image/jpeg"

  • You use item.getAsFile() to get a File object

  • You can create an object URL to display it

Common clipboard scenarios include:

  • Screenshots creating "image/png" items

  • Web images often creating multiple clipboard items

  • Images from editors coming with their appropriate MIME type