Skip to main content

Add File Sharing to Your React App

March 28, 2026 - EasySend Team

Adding file sharing to a React app usually means integrating a cloud storage SDK, configuring authentication and managing presigned URLs. With the EasySend API you skip all of that. No API key, no backend proxy, no CORS configuration. Just call the endpoint from your frontend and you get a shareable link back.

The Simplest Upload Component

Here is a minimal working component that uploads a file and displays the share link:

import { useState } from 'react';

function FileUploader() {
  const [shareUrl, setShareUrl] = useState(null);
  const [uploading, setUploading] = useState(false);

  async function handleUpload(e) {
    const file = e.target.files[0];
    if (!file) return;

    setUploading(true);
    const formData = new FormData();
    formData.append('files[]', file);

    const res = await fetch('https://easysend.co/api/v1/upload', {
      method: 'POST',
      body: formData,
    });

    const data = await res.json();
    setShareUrl(`https://easysend.co/${data.short_code}`);
    setUploading(false);
  }

  return (
    <div>
      <input type="file" onChange={handleUpload} disabled={uploading} />
      {uploading && <p>Uploading...</p>}
      {shareUrl && (
        <p>
          Share link: <a href={shareUrl}>{shareUrl}</a>
        </p>
      )}
    </div>
  );
}

That is a fully working file sharing component. No API key needed. The EasySend API accepts unauthenticated requests from any origin.

Adding Drag-and-Drop

File inputs work but drag-and-drop feels better. Here is how to add a drop zone:

import { useState, useCallback } from 'react';

function DropZone() {
  const [files, setFiles] = useState([]);
  const [shareUrl, setShareUrl] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [dragActive, setDragActive] = useState(false);

  const handleDrag = useCallback((e) => {
    e.preventDefault();
    e.stopPropagation();
    if (e.type === 'dragenter' || e.type === 'dragover') {
      setDragActive(true);
    } else if (e.type === 'dragleave') {
      setDragActive(false);
    }
  }, []);

  const handleDrop = useCallback((e) => {
    e.preventDefault();
    e.stopPropagation();
    setDragActive(false);

    const droppedFiles = Array.from(e.dataTransfer.files);
    if (droppedFiles.length > 0) {
      setFiles(droppedFiles);
    }
  }, []);

  const handleFileInput = useCallback((e) => {
    const selected = Array.from(e.target.files);
    if (selected.length > 0) {
      setFiles(selected);
    }
  }, []);

  async function uploadFiles() {
    if (files.length === 0) return;

    setUploading(true);
    const formData = new FormData();
    files.forEach((file) => formData.append('files[]', file));

    try {
      const res = await fetch('https://easysend.co/api/v1/upload', {
        method: 'POST',
        body: formData,
      });
      const data = await res.json();

      if (data.success) {
        setShareUrl(`https://easysend.co/${data.short_code}`);
      }
    } catch (err) {
      console.error('Upload failed:', err);
    } finally {
      setUploading(false);
    }
  }

  return (
    <div>
      <div
        onDragEnter={handleDrag}
        onDragLeave={handleDrag}
        onDragOver={handleDrag}
        onDrop={handleDrop}
        style={{
          border: `2px dashed ${dragActive ? '#00d4ff' : '#555'}`,
          borderRadius: '12px',
          padding: '2rem',
          textAlign: 'center',
          cursor: 'pointer',
          transition: 'border-color 0.2s',
        }}
      >
        <p>Drag files here or click to browse</p>
        <input
          type="file"
          multiple
          onChange={handleFileInput}
          style={{ display: 'none' }}
          id="file-input"
        />
        <label htmlFor="file-input" style={{ cursor: 'pointer', color: '#00d4ff' }}>
          Browse files
        </label>
      </div>

      {files.length > 0 && (
        <div style={{ marginTop: '1rem' }}>
          <p>{files.length} file(s) selected:</p>
          <ul>
            {files.map((f, i) => (
              <li key={i}>{f.name} ({(f.size / 1024).toFixed(1)} KB)</li>
            ))}
          </ul>
          <button onClick={uploadFiles} disabled={uploading}>
            {uploading ? 'Uploading...' : 'Upload & Share'}
          </button>
        </div>
      )}

      {shareUrl && (
        <div style={{ marginTop: '1rem', padding: '1rem', background: '#1a1a2e', borderRadius: '8px' }}>
          <p>Share link:</p>
          <a href={shareUrl} style={{ color: '#00d4ff' }}>{shareUrl}</a>
          <button onClick={() => navigator.clipboard.writeText(shareUrl)} style={{ marginLeft: '0.5rem' }}>
            Copy
          </button>
        </div>
      )}
    </div>
  );
}

This component handles drag events, file selection, multi-file uploads and displays the result with a copy button. It covers the most common file sharing UX patterns.

Adding Progress Tracking

For large files users need to see upload progress. The fetch API does not expose upload progress natively, so use XMLHttpRequest instead:

function useUpload() {
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);
  const [shareUrl, setShareUrl] = useState(null);
  const [error, setError] = useState(null);

  function upload(files) {
    return new Promise((resolve, reject) => {
      setUploading(true);
      setProgress(0);
      setError(null);

      const formData = new FormData();
      files.forEach((file) => formData.append('files[]', file));

      const xhr = new XMLHttpRequest();

      xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
          const pct = Math.round((e.loaded / e.total) * 100);
          setProgress(pct);
        }
      });

      xhr.addEventListener('load', () => {
        setUploading(false);
        if (xhr.status === 200) {
          const data = JSON.parse(xhr.responseText);
          if (data.success) {
            const url = `https://easysend.co/${data.short_code}`;
            setShareUrl(url);
            resolve(url);
          } else {
            setError('Upload failed');
            reject(new Error('Upload failed'));
          }
        } else {
          setError(`Server error: ${xhr.status}`);
          reject(new Error(`Server error: ${xhr.status}`));
        }
      });

      xhr.addEventListener('error', () => {
        setUploading(false);
        setError('Network error');
        reject(new Error('Network error'));
      });

      xhr.open('POST', 'https://easysend.co/api/v1/upload');
      xhr.send(formData);
    });
  }

  return { upload, progress, uploading, shareUrl, error };
}

Use this hook in any component:

function UploadWithProgress() {
  const { upload, progress, uploading, shareUrl, error } = useUpload();

  async function handleFiles(e) {
    const files = Array.from(e.target.files);
    await upload(files);
  }

  return (
    <div>
      <input type="file" multiple onChange={handleFiles} disabled={uploading} />

      {uploading && (
        <div style={{ marginTop: '1rem' }}>
          <div style={{
            width: '100%',
            height: '8px',
            background: '#333',
            borderRadius: '4px',
            overflow: 'hidden',
          }}>
            <div style={{
              width: `${progress}%`,
              height: '100%',
              background: '#00d4ff',
              transition: 'width 0.3s',
            }} />
          </div>
          <p>{progress}% uploaded</p>
        </div>
      )}

      {error && <p style={{ color: '#ff4466' }}>{error}</p>}

      {shareUrl && (
        <div style={{ marginTop: '1rem' }}>
          <p>Done! Share link:</p>
          <a href={shareUrl}>{shareUrl}</a>
        </div>
      )}
    </div>
  );
}

The Complete Component

Here is a production-ready component that combines drag-and-drop, progress tracking, error handling and a polished UI:

import { useState, useCallback } from 'react';

function EasySendUploader({ onUploadComplete }) {
  const [files, setFiles] = useState([]);
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);
  const [shareUrl, setShareUrl] = useState(null);
  const [error, setError] = useState(null);
  const [dragActive, setDragActive] = useState(false);
  const [copied, setCopied] = useState(false);

  const handleDrag = useCallback((e) => {
    e.preventDefault();
    e.stopPropagation();
    setDragActive(e.type === 'dragenter' || e.type === 'dragover');
  }, []);

  const addFiles = useCallback((newFiles) => {
    setFiles((prev) => [...prev, ...newFiles]);
    setShareUrl(null);
    setError(null);
  }, []);

  const handleDrop = useCallback((e) => {
    e.preventDefault();
    e.stopPropagation();
    setDragActive(false);
    addFiles(Array.from(e.dataTransfer.files));
  }, [addFiles]);

  const removeFile = useCallback((index) => {
    setFiles((prev) => prev.filter((_, i) => i !== index));
  }, []);

  function uploadFiles() {
    if (files.length === 0) return;

    setUploading(true);
    setProgress(0);
    setError(null);

    const formData = new FormData();
    files.forEach((file) => formData.append('files[]', file));

    const xhr = new XMLHttpRequest();

    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        setProgress(Math.round((e.loaded / e.total) * 100));
      }
    });

    xhr.addEventListener('load', () => {
      setUploading(false);
      if (xhr.status === 200) {
        const data = JSON.parse(xhr.responseText);
        if (data.success) {
          const url = `https://easysend.co/${data.short_code}`;
          setShareUrl(url);
          setFiles([]);
          if (onUploadComplete) onUploadComplete(url, data);
        } else {
          setError('Upload failed. Please try again.');
        }
      } else if (xhr.status === 429) {
        setError('Rate limited. Please wait a minute and try again.');
      } else {
        setError(`Upload failed (${xhr.status}). Please try again.`);
      }
    });

    xhr.addEventListener('error', () => {
      setUploading(false);
      setError('Network error. Check your connection and try again.');
    });

    xhr.open('POST', 'https://easysend.co/api/v1/upload');
    xhr.send(formData);
  }

  function copyLink() {
    navigator.clipboard.writeText(shareUrl);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }

  function reset() {
    setFiles([]);
    setShareUrl(null);
    setError(null);
    setProgress(0);
  }

  const totalSize = files.reduce((sum, f) => sum + f.size, 0);
  const formatSize = (bytes) => {
    if (bytes < 1024) return `${bytes} B`;
    if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
    return `${(bytes / 1048576).toFixed(1)} MB`;
  };

  return (
    <div style={{ maxWidth: '500px', margin: '0 auto' }}>
      {!shareUrl ? (
        <>
          <div
            onDragEnter={handleDrag}
            onDragLeave={handleDrag}
            onDragOver={handleDrag}
            onDrop={handleDrop}
            onClick={() => document.getElementById('es-file-input').click()}
            style={{
              border: `2px dashed ${dragActive ? '#00d4ff' : '#444'}`,
              borderRadius: '12px',
              padding: '3rem 2rem',
              textAlign: 'center',
              cursor: 'pointer',
              background: dragActive ? 'rgba(0, 212, 255, 0.05)' : 'transparent',
              transition: 'all 0.2s',
            }}
          >
            <p style={{ fontSize: '1.1rem', marginBottom: '0.5rem' }}>
              Drop files here or click to browse
            </p>
            <p style={{ fontSize: '0.85rem', opacity: 0.6 }}>
              No account needed. No API key.
            </p>
            <input
              id="es-file-input"
              type="file"
              multiple
              onChange={(e) => addFiles(Array.from(e.target.files))}
              style={{ display: 'none' }}
            />
          </div>

          {files.length > 0 && (
            <div style={{ marginTop: '1rem' }}>
              {files.map((f, i) => (
                <div key={i} style={{
                  display: 'flex',
                  justifyContent: 'space-between',
                  alignItems: 'center',
                  padding: '0.5rem 0',
                  borderBottom: '1px solid #333',
                }}>
                  <span>{f.name} ({formatSize(f.size)})</span>
                  <button
                    onClick={() => removeFile(i)}
                    style={{ background: 'none', border: 'none', color: '#ff4466', cursor: 'pointer' }}
                  >
                    Remove
                  </button>
                </div>
              ))}
              <p style={{ fontSize: '0.85rem', opacity: 0.6, marginTop: '0.5rem' }}>
                {files.length} file(s) - {formatSize(totalSize)} total
              </p>

              {uploading ? (
                <div style={{ marginTop: '1rem' }}>
                  <div style={{
                    width: '100%',
                    height: '6px',
                    background: '#333',
                    borderRadius: '3px',
                    overflow: 'hidden',
                  }}>
                    <div style={{
                      width: `${progress}%`,
                      height: '100%',
                      background: '#00d4ff',
                      transition: 'width 0.3s',
                    }} />
                  </div>
                  <p style={{ fontSize: '0.85rem', marginTop: '0.5rem' }}>
                    Uploading... {progress}%
                  </p>
                </div>
              ) : (
                <button
                  onClick={uploadFiles}
                  style={{
                    marginTop: '1rem',
                    width: '100%',
                    padding: '0.75rem',
                    background: '#00d4ff',
                    color: '#000',
                    border: 'none',
                    borderRadius: '8px',
                    fontSize: '1rem',
                    fontWeight: 600,
                    cursor: 'pointer',
                  }}
                >
                  Upload & Get Share Link
                </button>
              )}
            </div>
          )}
        </>
      ) : (
        <div style={{ textAlign: 'center', padding: '2rem' }}>
          <p style={{ fontSize: '1.2rem', marginBottom: '1rem' }}>Files shared!</p>
          <div style={{
            padding: '1rem',
            background: '#1a1a2e',
            borderRadius: '8px',
            marginBottom: '1rem',
          }}>
            <a href={shareUrl} style={{ color: '#00d4ff', fontSize: '1.1rem' }}>
              {shareUrl}
            </a>
          </div>
          <button onClick={copyLink} style={{
            padding: '0.5rem 1.5rem',
            background: copied ? '#22c55e' : '#00d4ff',
            color: '#000',
            border: 'none',
            borderRadius: '8px',
            fontWeight: 600,
            cursor: 'pointer',
            marginRight: '0.5rem',
          }}>
            {copied ? 'Copied!' : 'Copy Link'}
          </button>
          <button onClick={reset} style={{
            padding: '0.5rem 1.5rem',
            background: 'transparent',
            color: '#00d4ff',
            border: '1px solid #00d4ff',
            borderRadius: '8px',
            fontWeight: 600,
            cursor: 'pointer',
          }}>
            Upload More
          </button>
        </div>
      )}

      {error && (
        <p style={{ color: '#ff4466', marginTop: '1rem', textAlign: 'center' }}>
          {error}
        </p>
      )}
    </div>
  );
}

export default EasySendUploader;

Using the Component

Drop it into any React app:

import EasySendUploader from './EasySendUploader';

function App() {
  function handleComplete(url, data) {
    console.log('Uploaded to:', url);
    console.log('Files:', data.files.length);
    console.log('Short code:', data.short_code);
  }

  return (
    <div>
      <h1>Share Files</h1>
      <EasySendUploader onUploadComplete={handleComplete} />
    </div>
  );
}

The onUploadComplete callback gives you the share URL and the full API response so you can integrate with your own app logic. Store the link in your database, show it in a chat UI, send it via email or do whatever you need.

Why No API Key?

The EasySend developer API is designed for simplicity. There is no API key, no OAuth flow and no backend proxy needed. You call the endpoint directly from your frontend code. This means:

Rate limits are applied per IP address (10 uploads per hour) to prevent abuse. For higher limits in production apps, contact us.

Fetching Bundle Info

After uploading you might want to show file details. Use the bundle endpoint:

async function getBundleInfo(shortCode) {
  const res = await fetch(`https://easysend.co/api/v1/bundle/${shortCode}`);
  const data = await res.json();

  return {
    code: data.short_code,
    files: data.files.map((f) => ({
      id: f.id,
      name: f.name,
      size: f.size,
      downloads: f.download_count,
    })),
    expiresAt: data.expires_at,
  };
}

Adding File Sharing to Existing Apps

If you already have a React app and just want to add a "Share via link" button, here is the minimal version:

function ShareButton({ file }) {
  const [url, setUrl] = useState(null);
  const [loading, setLoading] = useState(false);

  async function share() {
    setLoading(true);
    const formData = new FormData();
    formData.append('files[]', file);

    const res = await fetch('https://easysend.co/api/v1/upload', {
      method: 'POST',
      body: formData,
    });
    const data = await res.json();
    const shareUrl = `https://easysend.co/${data.short_code}`;
    setUrl(shareUrl);
    setLoading(false);
    navigator.clipboard.writeText(shareUrl);
  }

  if (url) return <span>Link copied!</span>;
  return (
    <button onClick={share} disabled={loading}>
      {loading ? 'Sharing...' : 'Share via Link'}
    </button>
  );
}

Pass any File object from an input, a canvas blob or a programmatically created file. The component handles the upload and copies the link to the clipboard automatically.

For more integration patterns including server-side rendering, Next.js API routes and the embeddable widget, check out the integration guide.

Related Guides

Get notified about new features and tips

No spam. Unsubscribe anytime.

More from the blog

How to Share Large Files for Free in 2026
Mar 26, 2026
E2E Encrypted File Sharing: Why It Matters
Mar 26, 2026
The Developer's Guide to EasySend API
Mar 26, 2026