Uploading images to Cloudinary from the client side can elevate your React applications by streamlining the process of storing and managing media. This blog provides a detailed, user-friendly guide to implementing this feature using React and TypeScript.
Prerequisites
Before getting started, ensure you have:
- Basic familiarity with React and TypeScript.
- A Cloudinary account. If you don’t have one, you can sign up here.
Why Use Cloudinary for Image Uploads?
Cloudinary is a powerful media management platform that:
- Supports image and video storage.
- Provides tools for transformations, optimizations, and delivery.
- Offers a simple API for seamless integration.
By leveraging Cloudinary, you can save time and resources while providing a high-quality user experience.
Steps to Implement Image Upload to Cloudinary
1. Configure Cloudinary
- Log in to your Cloudinary account and navigate to the Dashboard.
- Copy your Cloud Name (you’ll need this for API calls).
- Go to Settings > Upload and create an unsigned upload preset. Note down the preset name for later use.
2. Set Up Your React Project
Begin by creating a new React application and installing Axios:
npm create vite@latest image-upload -- --template react
cd image-upload
npm install axios
3. Create Utility File for Upload Logic
To handle uploads in a reusable way, create a utility function for Cloudinary interactions.
utils/uploadImage.ts
import axios from 'axios';
const CLOUD_NAME = "your-cloud-name"; // Replace with your Cloudinary cloud name
const UPLOAD_PRESET = "your-upload-preset"; // Replace with your upload preset
export const uploadImageToCloudinary = async (image: File): Promise<any> => {
const formData = new FormData();
formData.append('file', image);
formData.append('upload_preset', UPLOAD_PRESET);
const response = await axios.post(
`https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload`,
formData
);
return response.data;
};
4. Create the Image Upload Component
Organize your project by creating a components
directory. Inside it, add a new file named ImageUpload.tsx
.
components/ImageUpload.tsx
import React, { useState } from "react";
import { uploadImageToCloudinary } from "../utils/uploadImage";
interface ImageUploadProps {
setUrl: (url: string) => void;
}
const ImageUpload: React.FC<ImageUploadProps> = ({ setUrl }) => {
const [image, setImage] = useState<File | null>(null);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0] || null;
if (file) {
// Validate file type and size (e.g., max 5MB)
const validTypes = ["image/jpeg", "image/png", "image/gif"];
if (!validTypes.includes(file.type)) {
setError("Please upload a valid image (JPEG, PNG, GIF).");
return;
}
if (file.size > 5 * 1024 * 1024) {
setError("Image size must be less than 5MB.");
return;
}
setImage(file);
setPreviewUrl(URL.createObjectURL(file));
setError(null);
}
};
const handleUpload = async () => {
if (!image) return;
try {
setIsUploading(true);
const data = await uploadImageToCloudinary(image);
setUrl(data.secure_url);
alert("Image uploaded successfully!");
} catch (error) {
console.error("Error uploading image:", error);
alert("Failed to upload the image. Please try again.");
} finally {
setIsUploading(false);
}
};
return (
<div style={styles.container}>
<h1 style={styles.heading}>Upload Image to Cloudinary</h1>
<input
type="file"
accept="image/*"
onChange={handleImageChange}
style={styles.fileInput}
/>
{error && <p style={styles.errorText}>{error}</p>}
{previewUrl && (
<img src={previewUrl} alt="Preview" style={styles.previewImage} />
)}
<button
onClick={handleUpload}
disabled={isUploading}
style={{
...styles.uploadButton,
backgroundColor: isUploading ? "#ccc" : "#007BFF",
cursor: isUploading ? "not-allowed" : "pointer",
}}
>
{isUploading ? (
<span>
<span className="spinner" style={styles.spinner}></span>
Uploading...
</span>
) : (
"Upload"
)}
</button>
</div>
);
};
const styles = {
container: {
maxWidth: "600px",
margin: "auto",
textAlign: "center" as const,
fontFamily: "Arial, sans-serif",
padding: "20px",
border: "1px solid #ddd",
borderRadius: "10px",
boxShadow: "0 4px 8px rgba(0,0,0,0.1)",
},
heading: {
marginBottom: "20px",
},
fileInput: {
display: "block",
margin: "20px auto",
},
previewImage: {
width: "100%",
height: "auto",
marginBottom: "20px",
borderRadius: "8px",
},
uploadButton: {
padding: "10px 20px",
color: "#fff",
border: "none",
borderRadius: "5px",
fontSize: "16px",
},
errorText: {
color: "red",
fontSize: "14px",
marginBottom: "10px",
},
spinner: {
width: "16px",
height: "16px",
marginRight: "5px",
border: "2px solid #fff",
borderTop: "2px solid #007BFF",
borderRadius: "50%",
display: "inline-block",
animation: "spin 1s linear infinite",
},
};
export default ImageUpload;
5. Integrate the Component
To include the ImageUpload
component in your application, update App.tsx
:
import { useState } from "react";
import ImageUpload from "./components/ImageUpload";
const App = () => {
const [url, setUrl] = useState("");
return (
<div style={{ fontFamily: "Arial, sans-serif", padding: "20px" }}>
<ImageUpload setUrl={setUrl} />
{url && (
<div style={{ marginTop: "20px", textAlign: "center" }}>
<h2>Uploaded Image URL:</h2>
<a href={url} target="_blank" rel="noopener noreferrer">
{url}
</a>
</div>
)}
</div>
);
};
export default App;
6. Run Your Application
Start the development server with:
npm run dev
Open http://localhost:5173
in your browser to test the image upload functionality. You can select an image, preview it, and upload it to Cloudinary.
This guide demonstrated how to build a feature-rich image upload component using React and TypeScript, integrated with Cloudinary. By keeping the image handling logic in the same file and abstracting the upload logic into a utility function, the code is easy to maintain and extend. Experiment with additional features like real-time transformations or backend integrations to unlock the full potential of Cloudinary!
Top comments (0)