Skip to content
Go back

Implementing DALL-E Image Generation in React

Implementing DALL-E Image Generation in React

Introduction

OpenAI’s DALL-E creates images from text descriptions. This guide integrates DALL-E into React applications for AI-powered image generation.

Prerequisites

Step 1: Install OpenAI SDK

npm install openai

Step 2: Create Image Generation API

Create pages/api/generate-image.ts (or app/api/generate-image/route.ts for App Router):

import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
});

export async function POST(req: NextRequest) {
  try {
    const { prompt, size = '1024x1024', quality = 'standard' } = await req.json();

    if (!prompt) {
      return NextResponse.json(
        { error: 'Prompt is required' },
        { status: 400 }
      );
    }

    const response = await openai.images.generate({
      model: 'dall-e-3',
      prompt: prompt,
      size: size as '1024x1024' | '1792x1024' | '1024x1792',
      quality: quality as 'standard' | 'hd',
      n: 1,
      response_format: 'url',
    });

    const imageUrl = response.data[0]?.url;

    if (!imageUrl) {
      throw new Error('No image generated');
    }

    return NextResponse.json({
      imageUrl,
      revisedPrompt: response.data[0]?.revised_prompt,
    });
  } catch (error) {
    console.error('DALL-E API error:', error);
    
    if (error instanceof Error) {
      return NextResponse.json(
        { error: error.message },
        { status: 500 }
      );
    }
    
    return NextResponse.json(
      { error: 'Failed to generate image' },
      { status: 500 }
    );
  }
}

Step 3: Create Image Generation Hook

Create hooks/useImageGeneration.ts:

import { useState } from 'react';

interface GenerateImageParams {
  prompt: string;
  size?: '1024x1024' | '1792x1024' | '1024x1792';
  quality?: 'standard' | 'hd';
}

interface GeneratedImage {
  imageUrl: string;
  revisedPrompt?: string;
}

export function useImageGeneration() {
  const [isGenerating, setIsGenerating] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const generateImage = async (params: GenerateImageParams): Promise<GeneratedImage | null> => {
    setIsGenerating(true);
    setError(null);

    try {
      const response = await fetch('/api/generate-image', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(params),
      });

      const data = await response.json();

      if (data.error) {
        throw new Error(data.error);
      }

      return {
        imageUrl: data.imageUrl,
        revisedPrompt: data.revisedPrompt,
      };
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Failed to generate image';
      setError(errorMessage);
      return null;
    } finally {
      setIsGenerating(false);
    }
  };

  return { generateImage, isGenerating, error };
}

Step 4: Create Image Generator Component

Create components/ImageGenerator.tsx:

'use client';

import { useState } from 'react';
import { useImageGeneration } from '@/hooks/useImageGeneration';

interface GeneratedImage {
  id: string;
  imageUrl: string;
  prompt: string;
  revisedPrompt?: string;
  timestamp: Date;
}

export default function ImageGenerator() {
  const [prompt, setPrompt] = useState('');
  const [size, setSize] = useState<'1024x1024' | '1792x1024' | '1024x1792'>('1024x1024');
  const [quality, setQuality] = useState<'standard' | 'hd'>('standard');
  const [images, setImages] = useState<GeneratedImage[]>([]);
  
  const { generateImage, isGenerating, error } = useImageGeneration();

  const handleGenerate = async () => {
    if (!prompt.trim() || isGenerating) return;

    const result = await generateImage({ prompt, size, quality });
    
    if (result) {
      const newImage: GeneratedImage = {
        id: Date.now().toString(),
        imageUrl: result.imageUrl,
        prompt,
        revisedPrompt: result.revisedPrompt,
        timestamp: new Date(),
      };
      
      setImages(prev => [newImage, ...prev]);
      setPrompt(''); // Clear prompt after successful generation
    }
  };

  const downloadImage = async (imageUrl: string, filename: string) => {
    try {
      const response = await fetch(imageUrl);
      const blob = await response.blob();
      const url = URL.createObjectURL(blob);
      
      const link = document.createElement('a');
      link.href = url;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(url);
    } catch (error) {
      console.error('Download failed:', error);
    }
  };

  return (
    <div className="max-w-4xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-8">AI Image Generator</h1>
      
      {/* Generation Form */}
      <div className="mb-8 space-y-4">
        <div>
          <label htmlFor="prompt" className="block text-sm font-medium mb-2">
            Describe the image you want to generate:
          </label>
          <textarea
            id="prompt"
            value={prompt}
            onChange={(e) => setPrompt(e.target.value)}
            placeholder="A serene landscape with mountains and a lake at sunset..."
            className="w-full p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[100px]"
            disabled={isGenerating}
          />
        </div>
        
        <div className="flex gap-4">
          <div>
            <label htmlFor="size" className="block text-sm font-medium mb-2">
              Size:
            </label>
            <select
              id="size"
              value={size}
              onChange={(e) => setSize(e.target.value as typeof size)}
              className="p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
              disabled={isGenerating}
            >
              <option value="1024x1024">Square (1024×1024)</option>
              <option value="1792x1024">Landscape (1792×1024)</option>
              <option value="1024x1792">Portrait (1024×1792)</option>
            </select>
          </div>
          
          <div>
            <label htmlFor="quality" className="block text-sm font-medium mb-2">
              Quality:
            </label>
            <select
              id="quality"
              value={quality}
              onChange={(e) => setQuality(e.target.value as typeof quality)}
              className="p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
              disabled={isGenerating}
            >
              <option value="standard">Standard</option>
              <option value="hd">HD</option>
            </select>
          </div>
        </div>
        
        <button
          onClick={handleGenerate}
          disabled={isGenerating || !prompt.trim()}
          className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300 font-semibold"
        >
          {isGenerating ? 'Generating...' : 'Generate Image'}
        </button>
      </div>

      {/* Error Display */}
      {error && (
        <div className="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg">
          Error: {error}
        </div>
      )}

      {/* Loading Indicator */}
      {isGenerating && (
        <div className="mb-6 p-4 bg-blue-100 border border-blue-400 text-blue-700 rounded-lg">
          <div className="flex items-center gap-3">
            <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
            Generating your image... This may take 10-20 seconds.
          </div>
        </div>
      )}

      {/* Generated Images */}
      {images.length > 0 && (
        <div className="space-y-8">
          <h2 className="text-2xl font-semibold">Generated Images</h2>
          
          {images.map((image) => (
            <div key={image.id} className="border rounded-lg overflow-hidden">
              <div className="aspect-square md:aspect-auto">
                <img
                  src={image.imageUrl}
                  alt={image.prompt}
                  className="w-full h-full object-contain bg-gray-100"
                  loading="lazy"
                />
              </div>
              
              <div className="p-4 space-y-3">
                <div>
                  <h3 className="font-semibold text-lg mb-1">Original Prompt:</h3>
                  <p className="text-gray-700">{image.prompt}</p>
                </div>
                
                {image.revisedPrompt && image.revisedPrompt !== image.prompt && (
                  <div>
                    <h4 className="font-semibold mb-1">AI-Revised Prompt:</h4>
                    <p className="text-gray-600 text-sm">{image.revisedPrompt}</p>
                  </div>
                )}
                
                <div className="flex justify-between items-center pt-2 border-t">
                  <span className="text-sm text-gray-500">
                    Generated: {image.timestamp.toLocaleString()}
                  </span>
                  
                  <button
                    onClick={() => downloadImage(
                      image.imageUrl,
                      `generated-image-${image.id}.png`
                    )}
                    className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 text-sm font-semibold"
                  >
                    Download
                  </button>
                </div>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Step 5: Prompt Engineering Helper

Create utils/promptEnhancements.ts:

export const promptStyles = [
  'photorealistic',
  'oil painting',
  'watercolor',
  'digital art',
  'pencil sketch',
  'cartoon style',
  'anime style',
  'minimalist',
  'vintage',
  'cyberpunk',
];

export const promptModifiers = [
  'highly detailed',
  'cinematic lighting',
  '4K resolution',
  'trending on artstation',
  'masterpiece',
  'vibrant colors',
  'dramatic shadows',
  'professional photography',
];

export function enhancePrompt(basePrompt: string, style?: string, modifiers?: string[]): string {
  let enhanced = basePrompt;
  
  if (style) {
    enhanced += `, ${style}`;
  }
  
  if (modifiers && modifiers.length > 0) {
    enhanced += `, ${modifiers.join(', ')}`;
  }
  
  return enhanced;
}

Step 6: Use in App

import ImageGenerator from '@/components/ImageGenerator';

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-50">
      <ImageGenerator />
    </main>
  );
}

Summary

DALL-E integration enables AI-powered image creation with customizable sizes and quality. Use prompt engineering techniques and provide download functionality for generated images to create compelling user experiences.


Share this post on:

Previous Post
Creating AI Embeddings with OpenAI for Semantic Search
Next Post
Deploying a Serverless API with AWS Lambda + API Gateway