AI Code Generation Tutorial
Master Trae's advanced AI code generation capabilities to write code faster and more efficiently.
Overview
Trae's AI code generation goes beyond simple autocomplete. It can:
- Generate entire functions and classes
- Create components from descriptions
- Build complete features
- Generate tests automatically
- Create documentation
- Refactor existing code
Getting Started
Enabling Code Generation
- Open Settings:
Ctrl+,(Windows/Linux) orCmd+,(Mac) - Navigate to: AI > Code Generation
- Enable features:
- ✅ Auto-completion
- ✅ Function generation
- ✅ Component scaffolding
- ✅ Test generation
Code Generation Triggers
Method 1: Comments
// Generate a function to validate email addresses
// AI will generate the function below this commentMethod 2: Function Signatures
function validateEmail(email: string): boolean {
// AI will complete the implementation
}Method 3: AI Chat
- Open AI chat (
Ctrl+Shift+A) - Request specific code generation
Method 4: Code Actions
- Right-click in editor
- Select "Generate with AI"
Basic Code Generation
1. Function Generation
Input:
// Create a function to debounce API calls
function debounceGenerated Output:
function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// Usage example:
const debouncedSearch = debounce((query: string) => {
console.log('Searching for:', query);
}, 300);2. Class Generation
Input:
// Create a User class with authentication methods
class User {Generated Output:
class User {
private id: string;
private email: string;
private passwordHash: string;
private isActive: boolean;
private createdAt: Date;
constructor(email: string, password: string) {
this.id = this.generateId();
this.email = email;
this.passwordHash = this.hashPassword(password);
this.isActive = true;
this.createdAt = new Date();
}
private generateId(): string {
return Math.random().toString(36).substr(2, 9);
}
private hashPassword(password: string): string {
// In production, use bcrypt or similar
return btoa(password + 'salt');
}
public authenticate(password: string): boolean {
return this.passwordHash === this.hashPassword(password);
}
public activate(): void {
this.isActive = true;
}
public deactivate(): void {
this.isActive = false;
}
public getProfile(): { id: string; email: string; isActive: boolean } {
return {
id: this.id,
email: this.email,
isActive: this.isActive
};
}
}React Component Generation
1. Simple Component
Input:
// Create a loading spinner component
const LoadingSpinnerGenerated Output:
import React from 'react';
import './LoadingSpinner.css';
interface LoadingSpinnerProps {
size?: 'small' | 'medium' | 'large';
color?: string;
message?: string;
}
const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
size = 'medium',
color = '#3498db',
message = 'Loading...'
}) => {
const sizeClasses = {
small: 'spinner-small',
medium: 'spinner-medium',
large: 'spinner-large'
};
return (
<div className="loading-container">
<div
className={`spinner ${sizeClasses[size]}`}
style={{ borderTopColor: color }}
/>
{message && <p className="loading-message">{message}</p>}
</div>
);
};
export default LoadingSpinner;Generated CSS:
/* LoadingSpinner.css */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.spinner-small {
width: 20px;
height: 20px;
border-width: 2px;
}
.spinner-medium {
width: 40px;
height: 40px;
}
.spinner-large {
width: 60px;
height: 60px;
border-width: 6px;
}
.loading-message {
margin-top: 10px;
color: #666;
font-size: 14px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}2. Complex Component with Hooks
Input:
// Create a search component with debounced input and results
const SearchComponentGenerated Output:
import React, { useState, useEffect, useMemo } from 'react';
import { debounce } from '../utils/debounce';
interface SearchResult {
id: string;
title: string;
description: string;
url?: string;
}
interface SearchComponentProps {
onSearch: (query: string) => Promise<SearchResult[]>;
placeholder?: string;
debounceMs?: number;
maxResults?: number;
}
const SearchComponent: React.FC<SearchComponentProps> = ({
onSearch,
placeholder = 'Search...',
debounceMs = 300,
maxResults = 10
}) => {
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const debouncedSearch = useMemo(
() => debounce(async (searchQuery: string) => {
if (!searchQuery.trim()) {
setResults([]);
return;
}
setLoading(true);
setError(null);
try {
const searchResults = await onSearch(searchQuery);
setResults(searchResults.slice(0, maxResults));
} catch (err) {
setError('Search failed. Please try again.');
setResults([]);
} finally {
setLoading(false);
}
}, debounceMs),
[onSearch, debounceMs, maxResults]
);
useEffect(() => {
debouncedSearch(query);
}, [query, debouncedSearch]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
};
const handleResultClick = (result: SearchResult) => {
if (result.url) {
window.open(result.url, '_blank');
}
};
return (
<div className="search-component">
<div className="search-input-container">
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder={placeholder}
className="search-input"
/>
{loading && <div className="search-loading">🔍</div>}
</div>
{error && (
<div className="search-error">
{error}
</div>
)}
{results.length > 0 && (
<div className="search-results">
{results.map((result) => (
<div
key={result.id}
className="search-result-item"
onClick={() => handleResultClick(result)}
>
<h4 className="result-title">{result.title}</h4>
<p className="result-description">{result.description}</p>
</div>
))}
</div>
)}
{query && !loading && results.length === 0 && !error && (
<div className="no-results">
No results found for "{query}"
</div>
)}
</div>
);
};
export default SearchComponent;Advanced Code Generation
1. API Integration
Input:
// Create a service class for user management API
class UserService {Generated Output:
interface User {
id: string;
email: string;
name: string;
createdAt: string;
updatedAt: string;
}
interface CreateUserRequest {
email: string;
name: string;
password: string;
}
interface UpdateUserRequest {
name?: string;
email?: string;
}
class UserService {
private baseUrl: string;
private apiKey: string;
constructor(baseUrl: string, apiKey: string) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
}
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const config: RequestInit = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
...options.headers,
},
...options,
};
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
async getUsers(): Promise<User[]> {
return this.request<User[]>('/users');
}
async getUserById(id: string): Promise<User> {
return this.request<User>(`/users/${id}`);
}
async createUser(userData: CreateUserRequest): Promise<User> {
return this.request<User>('/users', {
method: 'POST',
body: JSON.stringify(userData),
});
}
async updateUser(id: string, userData: UpdateUserRequest): Promise<User> {
return this.request<User>(`/users/${id}`, {
method: 'PUT',
body: JSON.stringify(userData),
});
}
async deleteUser(id: string): Promise<void> {
await this.request<void>(`/users/${id}`, {
method: 'DELETE',
});
}
async searchUsers(query: string): Promise<User[]> {
const params = new URLSearchParams({ q: query });
return this.request<User[]>(`/users/search?${params}`);
}
}
export default UserService;2. Custom Hooks Generation
Input:
// Create a custom hook for form validation
function useFormValidationGenerated Output:
import { useState, useCallback, useMemo } from 'react';
type ValidationRule<T> = {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
custom?: (value: T) => string | null;
};
type ValidationRules<T> = {
[K in keyof T]?: ValidationRule<T[K]>;
};
type ValidationErrors<T> = {
[K in keyof T]?: string;
};
interface UseFormValidationReturn<T> {
values: T;
errors: ValidationErrors<T>;
isValid: boolean;
isSubmitting: boolean;
setValue: <K extends keyof T>(field: K, value: T[K]) => void;
setValues: (values: Partial<T>) => void;
validateField: <K extends keyof T>(field: K) => boolean;
validateAll: () => boolean;
reset: () => void;
handleSubmit: (onSubmit: (values: T) => Promise<void> | void) => Promise<void>;
}
function useFormValidation<T extends Record<string, any>>(
initialValues: T,
validationRules: ValidationRules<T>
): UseFormValidationReturn<T> {
const [values, setValuesState] = useState<T>(initialValues);
const [errors, setErrors] = useState<ValidationErrors<T>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const validateField = useCallback(<K extends keyof T>(field: K): boolean => {
const value = values[field];
const rules = validationRules[field];
if (!rules) return true;
let error: string | null = null;
// Required validation
if (rules.required && (!value || (typeof value === 'string' && !value.trim()))) {
error = `${String(field)} is required`;
}
// String validations
if (!error && typeof value === 'string') {
if (rules.minLength && value.length < rules.minLength) {
error = `${String(field)} must be at least ${rules.minLength} characters`;
}
if (rules.maxLength && value.length > rules.maxLength) {
error = `${String(field)} must be no more than ${rules.maxLength} characters`;
}
if (rules.pattern && !rules.pattern.test(value)) {
error = `${String(field)} format is invalid`;
}
}
// Custom validation
if (!error && rules.custom) {
error = rules.custom(value);
}
setErrors(prev => ({
...prev,
[field]: error || undefined
}));
return !error;
}, [values, validationRules]);
const validateAll = useCallback((): boolean => {
const fields = Object.keys(validationRules) as (keyof T)[];
const results = fields.map(field => validateField(field));
return results.every(Boolean);
}, [validateField, validationRules]);
const setValue = useCallback(<K extends keyof T>(field: K, value: T[K]) => {
setValuesState(prev => ({ ...prev, [field]: value }));
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }));
}
}, [errors]);
const setValues = useCallback((newValues: Partial<T>) => {
setValuesState(prev => ({ ...prev, ...newValues }));
}, []);
const reset = useCallback(() => {
setValuesState(initialValues);
setErrors({});
setIsSubmitting(false);
}, [initialValues]);
const handleSubmit = useCallback(async (onSubmit: (values: T) => Promise<void> | void) => {
if (isSubmitting) return;
setIsSubmitting(true);
try {
if (validateAll()) {
await onSubmit(values);
}
} catch (error) {
console.error('Form submission error:', error);
} finally {
setIsSubmitting(false);
}
}, [values, validateAll, isSubmitting]);
const isValid = useMemo(() => {
return Object.keys(validationRules).every(field =>
!errors[field as keyof T]
);
}, [errors, validationRules]);
return {
values,
errors,
isValid,
isSubmitting,
setValue,
setValues,
validateField,
validateAll,
reset,
handleSubmit
};
}
export default useFormValidation;
// Usage example:
/*
interface LoginForm {
email: string;
password: string;
}
const LoginComponent = () => {
const {
values,
errors,
isValid,
isSubmitting,
setValue,
handleSubmit
} = useFormValidation<LoginForm>(
{ email: '', password: '' },
{
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
custom: (value) => {
if (value.includes('test')) {
return 'Test emails are not allowed';
}
return null;
}
},
password: {
required: true,
minLength: 8
}
}
);
const onSubmit = async (formData: LoginForm) => {
// Handle login
console.log('Logging in:', formData);
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit(onSubmit);
}}>
<input
type="email"
value={values.email}
onChange={(e) => setValue('email', e.target.value)}
placeholder="Email"
/>
{errors.email && <span>{errors.email}</span>}
<input
type="password"
value={values.password}
onChange={(e) => setValue('password', e.target.value)}
placeholder="Password"
/>
{errors.password && <span>{errors.password}</span>}
<button type="submit" disabled={!isValid || isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Login'}
</button>
</form>
);
};
*/Test Generation
1. Unit Tests
Input:
// Generate tests for the validateEmail function
// function validateEmail(email: string): booleanGenerated Output:
import { validateEmail } from './emailValidator';
describe('validateEmail', () => {
describe('valid emails', () => {
test('should accept standard email format', () => {
expect(validateEmail('user@example.com')).toBe(true);
expect(validateEmail('test.email@domain.org')).toBe(true);
expect(validateEmail('user+tag@example.co.uk')).toBe(true);
});
test('should accept emails with numbers', () => {
expect(validateEmail('user123@example.com')).toBe(true);
expect(validateEmail('123user@example.com')).toBe(true);
});
test('should accept emails with special characters', () => {
expect(validateEmail('user.name@example.com')).toBe(true);
expect(validateEmail('user+tag@example.com')).toBe(true);
expect(validateEmail('user_name@example.com')).toBe(true);
});
test('should accept emails with subdomains', () => {
expect(validateEmail('user@mail.example.com')).toBe(true);
expect(validateEmail('user@subdomain.domain.com')).toBe(true);
});
});
describe('invalid emails', () => {
test('should reject emails without @ symbol', () => {
expect(validateEmail('userexample.com')).toBe(false);
expect(validateEmail('user.example.com')).toBe(false);
});
test('should reject emails without domain', () => {
expect(validateEmail('user@')).toBe(false);
expect(validateEmail('user@.')).toBe(false);
});
test('should reject emails without local part', () => {
expect(validateEmail('@example.com')).toBe(false);
expect(validateEmail('.@example.com')).toBe(false);
});
test('should reject emails with invalid characters', () => {
expect(validateEmail('user name@example.com')).toBe(false);
expect(validateEmail('user@exam ple.com')).toBe(false);
expect(validateEmail('user@example..com')).toBe(false);
});
test('should reject empty or null inputs', () => {
expect(validateEmail('')).toBe(false);
expect(validateEmail(' ')).toBe(false);
});
test('should reject emails that are too long', () => {
const longEmail = 'a'.repeat(250) + '@example.com';
expect(validateEmail(longEmail)).toBe(false);
});
test('should reject emails with invalid domain formats', () => {
expect(validateEmail('user@example')).toBe(false);
expect(validateEmail('user@.com')).toBe(false);
expect(validateEmail('user@example.')).toBe(false);
});
});
describe('edge cases', () => {
test('should handle international domain names', () => {
expect(validateEmail('user@münchen.de')).toBe(true);
});
test('should handle quoted local parts', () => {
expect(validateEmail('"user name"@example.com')).toBe(true);
});
test('should handle IP address domains', () => {
expect(validateEmail('user@[192.168.1.1]')).toBe(true);
});
});
});2. Component Tests
Input:
// Generate tests for SearchComponentGenerated Output:
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import SearchComponent from './SearchComponent';
const mockSearchResults = [
{
id: '1',
title: 'First Result',
description: 'Description for first result',
url: 'https://example.com/1'
},
{
id: '2',
title: 'Second Result',
description: 'Description for second result',
url: 'https://example.com/2'
}
];
const mockOnSearch = jest.fn();
describe('SearchComponent', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test('renders search input with placeholder', () => {
render(<SearchComponent onSearch={mockOnSearch} placeholder="Search items..." />);
expect(screen.getByPlaceholderText('Search items...')).toBeInTheDocument();
});
test('calls onSearch with debounced input', async () => {
mockOnSearch.mockResolvedValue(mockSearchResults);
render(<SearchComponent onSearch={mockOnSearch} debounceMs={300} />);
const input = screen.getByPlaceholderText('Search...');
await userEvent.type(input, 'test query');
// Should not call immediately
expect(mockOnSearch).not.toHaveBeenCalled();
// Fast-forward time
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(mockOnSearch).toHaveBeenCalledWith('test query');
});
});
test('displays search results', async () => {
mockOnSearch.mockResolvedValue(mockSearchResults);
render(<SearchComponent onSearch={mockOnSearch} />);
const input = screen.getByPlaceholderText('Search...');
await userEvent.type(input, 'test');
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.getByText('First Result')).toBeInTheDocument();
expect(screen.getByText('Second Result')).toBeInTheDocument();
expect(screen.getByText('Description for first result')).toBeInTheDocument();
});
});
test('displays loading state during search', async () => {
mockOnSearch.mockImplementation(() => new Promise(resolve =>
setTimeout(() => resolve(mockSearchResults), 1000)
));
render(<SearchComponent onSearch={mockOnSearch} />);
const input = screen.getByPlaceholderText('Search...');
await userEvent.type(input, 'test');
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.getByText('🔍')).toBeInTheDocument();
});
});
test('displays error message on search failure', async () => {
mockOnSearch.mockRejectedValue(new Error('Search failed'));
render(<SearchComponent onSearch={mockOnSearch} />);
const input = screen.getByPlaceholderText('Search...');
await userEvent.type(input, 'test');
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.getByText('Search failed. Please try again.')).toBeInTheDocument();
});
});
test('displays no results message when search returns empty array', async () => {
mockOnSearch.mockResolvedValue([]);
render(<SearchComponent onSearch={mockOnSearch} />);
const input = screen.getByPlaceholderText('Search...');
await userEvent.type(input, 'nonexistent');
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.getByText('No results found for "nonexistent"')).toBeInTheDocument();
});
});
test('handles result click with URL', async () => {
mockOnSearch.mockResolvedValue(mockSearchResults);
const originalOpen = window.open;
window.open = jest.fn();
render(<SearchComponent onSearch={mockOnSearch} />);
const input = screen.getByPlaceholderText('Search...');
await userEvent.type(input, 'test');
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.getByText('First Result')).toBeInTheDocument();
});
fireEvent.click(screen.getByText('First Result'));
expect(window.open).toHaveBeenCalledWith('https://example.com/1', '_blank');
window.open = originalOpen;
});
test('respects maxResults prop', async () => {
const manyResults = Array.from({ length: 20 }, (_, i) => ({
id: `${i}`,
title: `Result ${i}`,
description: `Description ${i}`
}));
mockOnSearch.mockResolvedValue(manyResults);
render(<SearchComponent onSearch={mockOnSearch} maxResults={5} />);
const input = screen.getByPlaceholderText('Search...');
await userEvent.type(input, 'test');
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.getByText('Result 0')).toBeInTheDocument();
expect(screen.getByText('Result 4')).toBeInTheDocument();
expect(screen.queryByText('Result 5')).not.toBeInTheDocument();
});
});
test('clears results when input is empty', async () => {
mockOnSearch.mockResolvedValue(mockSearchResults);
render(<SearchComponent onSearch={mockOnSearch} />);
const input = screen.getByPlaceholderText('Search...');
// Type and get results
await userEvent.type(input, 'test');
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.getByText('First Result')).toBeInTheDocument();
});
// Clear input
await userEvent.clear(input);
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(screen.queryByText('First Result')).not.toBeInTheDocument();
});
});
});Code Generation Best Practices
1. Write Clear Comments
❌ Vague:
// Make a function✅ Specific:
// Create a function that validates password strength and returns a score from 0-100
// Should check for length, uppercase, lowercase, numbers, and special characters2. Provide Context
❌ No context:
function handleSubmit✅ With context:
// Handle form submission for user registration
// Should validate data, show loading state, and handle errors
function handleSubmit3. Specify Types
❌ Generic:
// Create a hook for API calls✅ Typed:
// Create a TypeScript hook for making API calls with loading and error states
// Should return { data: T | null, loading: boolean, error: string | null }4. Include Examples
// Create a utility function to format currency
// Example: formatCurrency(1234.56, 'USD') => '$1,234.56'
// Example: formatCurrency(1000, 'EUR') => '€1,000.00'
function formatCurrencyTroubleshooting
Common Issues
Generated code doesn't compile:
- Check TypeScript configuration
- Ensure all imports are available
- Verify type definitions
AI generates outdated patterns:
- Specify the framework version
- Mention modern best practices
- Ask for current approaches
Generated code is too complex:
- Break down into smaller requests
- Ask for simpler implementations
- Specify experience level
Getting Better Results
- Be specific about requirements
- Include type information for TypeScript
- Mention performance requirements
- Specify testing needs
- Ask for documentation when needed
Advanced Techniques
1. Iterative Refinement
Step 1: "Create a basic todo component"
Step 2: "Add drag and drop functionality"
Step 3: "Add categories and filtering"
Step 4: "Add persistence with localStorage"
Step 5: "Add animations and transitions"2. Pattern-Based Generation
// Generate a repository pattern for User entity
// Should include CRUD operations, search, and pagination
// Use TypeScript interfaces and error handling
class UserRepository3. Architecture Generation
// Create a complete feature module for user management
// Include: types, service, hooks, components, and tests
// Follow clean architecture principlesNext Steps
Now that you've mastered code generation:
- Practice with different types of code
- Experiment with various prompting techniques
- Combine with other Trae features
- Share your generated code with the team
Conclusion
Trae's AI code generation is a powerful tool that can significantly speed up your development process. The key to success is:
- Writing clear, specific prompts
- Providing adequate context
- Iterating and refining
- Combining AI generation with your expertise
Happy coding! 🚀✨