Appearance
React Tutorial
Learn how to integrate Icefiery into your React application for image uploads and transformations.
Using Next.js? Read Next.js tutorial instead!
Overview
This tutorial shows you how to:
- Upload images from React client components
- Create backend API routes to handle image saving
- Display and transform images in your React components
Prerequisites
- A React application
- API keys from your Icefiery dashboard
- Environment variables configured
Step 1: Environment Setup
Add your API keys to your environment variables:
bash
# .env (or .env.local for Create React App)
REACT_APP_ICEFIERY_UPLOAD_KEY=https://api.icefiery.com/api/v1/upload-temporary-image/<your_upload_key>
ICEFIERY_API_KEY=<your_api_key>
Or if you're using Docker:
bash
# .env (or .env.local for Create React App)
REACT_APP_ICEFIERY_UPLOAD_URL=http://localhost:5100/api/v1/upload-temporary-image/123456789
ICEFIERY_API_KEY=private_0000000000
Step 2: Create ImageInput Component
INFO
Not mandatory, but this component is a good base for your customizations. It includes:
- Loading state
- Error state
- Preview of uploaded image
Create a simple reusable ImageInput
component:
tsx
import { useState } from "react";
interface ImageInputProps {
uploadUrl: string;
onImageUploaded: (url: string) => void;
disabled?: boolean;
}
export const ImageInput: React.FC<ImageInputProps> = ({
uploadUrl,
onImageUploaded,
disabled,
}) => {
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleFileUpload = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
const file = event.target.files?.[0];
if (!file) return;
try {
setIsLoading(true);
setError(null);
const res = await fetch(uploadUrl, {
method: "POST",
headers: {
"X-Original-Filename": file.name,
},
body: file,
});
const data = await res.json();
const { temporaryImageUrl } = data;
onImageUploaded(temporaryImageUrl);
setPreviewUrl(temporaryImageUrl);
} catch (error) {
console.error("Upload failed:", error);
setError("Uploading image failed");
} finally {
setIsLoading(false);
}
};
return (
<>
<input
type="file"
accept="image/jpeg,image/png,image/webp,image/gif,image/avif,image/tiff,image/heic"
onChange={handleFileUpload}
disabled={isLoading || disabled}
/>
{error && <div>{error}</div>}
{previewUrl && (
<div>
<h3>Preview:</h3>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt="Preview"
src={`${previewUrl}?width=150&height=150`}
style={{
borderRadius: "4px",
}}
/>
</div>
)}
</>
);
};
jsx
import { useState } from "react";
export const ImageInput = ({ uploadUrl, onImageUploaded, disabled }) => {
const [previewUrl, setPreviewUrl] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const handleFileUpload = async (event) => {
const file = event.target.files?.[0];
if (!file) return;
try {
setIsLoading(true);
setError(null);
const res = await fetch(uploadUrl, {
method: "POST",
headers: {
"X-Original-Filename": file.name,
},
body: file,
});
const data = await res.json();
const { temporaryImageUrl } = data;
onImageUploaded(temporaryImageUrl);
setPreviewUrl(temporaryImageUrl);
} catch (error) {
console.error("Upload failed:", error);
setError("Uploading image failed");
} finally {
setIsLoading(false);
}
};
return (
<>
<input
type="file"
accept="image/jpeg,image/png,image/webp,image/gif,image/avif,image/tiff,image/heic"
onChange={handleFileUpload}
disabled={isLoading || disabled}
/>
{error && <div>{error}</div>}
{previewUrl && (
<div>
<h3>Preview:</h3>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt="Preview"
src={`${previewUrl}?width=150&height=150`}
style={{
borderRadius: "4px",
}}
/>
</div>
)}
</>
);
};
Step 3: Use the ImageInput Component
Now use the ImageInput
component in your page or form:
tsx
import { useState } from "react";
import { ImageInput } from "./ImageInput";
export default function ProfilePage() {
const [temporaryImageUrl, setTemporaryImageUrl] = useState<string>("");
return (
<div>
<h1>Update Profile Picture</h1>
<ImageInput
uploadUrl={`/api/v1/upload-temporary-image/${process.env.REACT_APP_ICEFIERY_UPLOAD_KEY}`}
onImageUploaded={setTemporaryImageUrl}
/>
{temporaryImageUrl && <p>Image uploaded: {temporaryImageUrl}</p>}
</div>
);
}
jsx
import { useState } from "react";
import { ImageInput } from "./ImageInput";
export default function ProfilePage() {
const [temporaryImageUrl, setTemporaryImageUrl] = useState("");
return (
<div>
<h1>Update Profile Picture</h1>
<ImageInput
uploadUrl={`/api/v1/upload-temporary-image/${process.env.REACT_APP_ICEFIERY_UPLOAD_KEY}`}
onImageUploaded={setTemporaryImageUrl}
/>
{temporaryImageUrl && <p>Image uploaded: {temporaryImageUrl}</p>}
</div>
);
}
Step 4: Create Backend API Route
After uploading an image, you'll receive a temporaryImageUrl
. This temporary URL expires after a short time, so you need to save it permanently to your project.
Create a backend API route to securely save the temporary image using your private API key.
ts
// app.ts
import express, { Request, Response } from "express";
import bodyParser from "body-parser";
const app = express();
app
.use(bodyParser.json())
.post("/api/update-profile-picture", async (req: Request, res: Response) => {
// Get from session/auth: req.user.id, etc.
const userId = "user_12345";
// Read temporaryImageUrl from body
const body: { temporaryImageUrl: string } = req.body;
const { temporaryImageUrl } = body;
// Save the temporary image permanently
const response = await fetch(
"https://api.icefiery.com/api/v1/save-temporary-image",
{
method: "POST",
headers: {
"X-API-Key": process.env.ICEFIERY_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
temporaryImageUrl,
metadata: {
// Optional metadata can be stored reference / debugging ease
userId,
},
}),
}
);
const { imageUrl }: { imageUrl: string } = await response.json();
// Update user profile in database
// await db.users.update(userId, {
// profilePictureUrl: imageUrl
// });
res.json({ imageUrl });
});
js
// app.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app
.use(bodyParser.json())
.post("/api/update-profile-picture", async (req, res) => {
// Get from session/auth: req.user.id, etc.
const userId = "user_12345";
// Read temporaryImageUrl from body
const { temporaryImageUrl } = req.body;
// Save the temporary image permanently
const response = await fetch(
"https://api.icefiery.com/api/v1/save-temporary-image",
{
method: "POST",
headers: {
"X-API-Key": process.env.ICEFIERY_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
temporaryImageUrl,
metadata: {
// Optional metadata can be stored reference / debugging ease
userId,
},
}),
}
);
const { imageUrl } = await response.json();
// Update user profile in database
// await db.users.update(userId, {
// profilePictureUrl: imageUrl
// });
res.json({ imageUrl });
});
Step 5: Save the Image with Form Submission
Now update your page to include a form that saves the temporary image permanently using your API route:
tsx
import { useState } from "react";
import { ImageInput } from "./ImageInput";
export default function ProfilePage() {
const [temporaryImageUrl, setTemporaryImageUrl] = useState<string>("");
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!temporaryImageUrl) return;
try {
setIsSubmitting(true);
const response = await fetch("/api/update-profile-picture", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ temporaryImageUrl }),
});
const { imageUrl } = await response.json();
console.log("Saved image:", imageUrl);
alert("Profile picture updated!");
} catch (error) {
console.error("Failed to save image:", error);
alert("Failed to update profile picture");
} finally {
setIsSubmitting(false);
}
};
return (
<div>
<h1>Update Profile Picture</h1>
<form onSubmit={handleSubmit}>
<ImageInput
uploadUrl={`/api/v1/upload-temporary-image/${process.env.REACT_APP_ICEFIERY_UPLOAD_KEY}`}
onImageUploaded={setTemporaryImageUrl}
disabled={isSubmitting}
/>
<button type="submit" disabled={!temporaryImageUrl || isSubmitting}>
{isSubmitting ? "Saving..." : "Save Profile Picture"}
</button>
</form>
</div>
);
}
jsx
import { useState } from "react";
import { ImageInput } from "./ImageInput";
export default function ProfilePage() {
const [temporaryImageUrl, setTemporaryImageUrl] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
if (!temporaryImageUrl) return;
try {
setIsSubmitting(true);
const response = await fetch("/api/update-profile-picture", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ temporaryImageUrl }),
});
const { imageUrl } = await response.json();
console.log("Saved image:", imageUrl);
alert("Profile picture updated!");
} catch (error) {
console.error("Failed to save image:", error);
alert("Failed to update profile picture");
} finally {
setIsSubmitting(false);
}
};
return (
<div>
<h1>Update Profile Picture</h1>
<form onSubmit={handleSubmit}>
<ImageInput
uploadUrl={`/api/v1/upload-temporary-image/${process.env.REACT_APP_ICEFIERY_UPLOAD_KEY}`}
onImageUploaded={setTemporaryImageUrl}
disabled={isSubmitting}
/>
<button type="submit" disabled={!temporaryImageUrl || isSubmitting}>
{isSubmitting ? "Saving..." : "Save Profile Picture"}
</button>
</form>
</div>
);
}
Step 6: Display and Transform Images
Use Icefiery URLs directly in your React components:
tsx
interface UserAvatarProps {
imageId: string;
size?: number;
}
function UserAvatar({ imageId, size = 100 }: UserAvatarProps) {
const imageUrl = `https://cdn.icefiery.com/res/${imageId}.jpg?width=${size}&height=${size}&resize=cover`;
return (
<img
src={imageUrl}
alt="User avatar"
width={size}
height={size}
style={{ borderRadius: "50%" }}
/>
);
}
interface ProductImageProps {
imageId: string;
}
function ProductImage({ imageId }: ProductImageProps) {
return (
<picture>
{/* WebP for modern browsers */}
<source
srcSet={`https://cdn.icefiery.com/res/${imageId}.webp?width=400&height=300`}
type="image/webp"
/>
{/* JPEG fallback */}
<img
src={`https://cdn.icefiery.com/res/${imageId}.jpg?width=400&height=300`}
alt="Product"
loading="lazy"
/>
</picture>
);
}
jsx
function UserAvatar({ imageId, size = 100 }) {
const imageUrl = `https://cdn.icefiery.com/res/${imageId}.jpg?width=${size}&height=${size}&resize=cover`;
return (
<img
src={imageUrl}
alt="User avatar"
width={size}
height={size}
style={{ borderRadius: "50%" }}
/>
);
}
function ProductImage({ imageId }) {
return (
<picture>
{/* WebP for modern browsers */}
<source
srcSet={`https://cdn.icefiery.com/res/${imageId}.webp?width=400&height=300`}
type="image/webp"
/>
{/* JPEG fallback */}
<img
src={`https://cdn.icefiery.com/res/${imageId}.jpg?width=400&height=300`}
alt="Product"
loading="lazy"
/>
</picture>
);
}
:::
All Done!
You've successfully integrated Icefiery into your React application! Your users can now upload images which are temporarily stored, then permanently saved to your project with proper error handling and loading states.
Next Steps
- Docker Tutorial - Run Icefiery locally for development purposes
- Managing API Keys - Set up secure access
- Organizing with Projects - Manage images effectively
- API Reference - Complete API documentation