Skip to content

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

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