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
- React 18+ or Next.js
- OpenAI API key
- Node.js >=18
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.