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:
- No backend server required for file sharing
- No secret management or environment variables
- No CORS issues since the API allows all origins
- Works in static sites, SPAs and server-rendered apps
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
- EasySend API Documentation - full endpoint reference
- Developer API Overview - rate limits, features and use cases
- Integration Guide - widgets, SDKs and embedding options
- Upload Files with Python in 3 Lines - Python integration tutorial
- Developer Guide to EasySend API - multi-language examples