This article contains affiliate links. I may earn a commission at no extra cost to you.
title: "Build Your First AI-Powered Todo App: React + OpenAI Complete Tutorial"
published: true
description: "Learn to build a smart todo app that automatically categorizes tasks and suggests priorities using React and OpenAI's API"
tags: ai, react, tutorial, beginners, openai
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ai-todo-cover.png
Ever wondered how to add AI superpowers to your web apps? Today we'll build a todo app that doesn't just store your tasksβit understands them. Our AI assistant will automatically categorize tasks, suggest priorities, and even recommend new tasks based on your patterns.
By the end of this tutorial, you'll have a working app deployed online and understand exactly how to integrate OpenAI's API into any React project.
What We're Building
Our smart todo app will:
- β Handle basic CRUD operations for tasks
- π€ Auto-categorize tasks (Work, Personal, Health, etc.)
- π Suggest priority levels (High, Medium, Low)
- π‘ Recommend new tasks based on existing ones
- β‘ Handle loading states and API errors gracefully
Prerequisites
- Basic React knowledge (hooks, state management)
- Node.js installed on your machine
- An OpenAI API key (get one here)
Step 1: Project Setup
Let's start with a fresh React app:
npx create-react-app ai-todo-app
cd ai-todo-app
npm install axios
npm start
Create a .env file in your project root:
REACT_APP_OPENAI_API_KEY=your_api_key_here
β οΈ Important: Never commit API keys to version control. Add .env to your .gitignore.
Step 2: Basic Todo Structure
Replace src/App.js with our foundation:
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const addTask = async () => {
if (!newTask.trim()) return;
setLoading(true);
setError('');
try {
const aiEnhancedTask = await enhanceTaskWithAI(newTask);
const task = {
id: Date.now(),
text: newTask,
completed: false,
category: aiEnhancedTask.category,
priority: aiEnhancedTask.priority,
createdAt: new Date().toISOString()
};
setTasks([...tasks, task]);
setNewTask('');
} catch (err) {
setError('Failed to process task. Please try again.');
} finally {
setLoading(false);
}
};
const toggleTask = (id) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id) => {
setTasks(tasks.filter(task => task.id !== id));
};
return (
<div className="App">
<header className="app-header">
<h1>π€ AI Todo Assistant</h1>
</header>
<main className="app-main">
<div className="task-input">
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="What needs to be done?"
onKeyPress={(e) => e.key === 'Enter' && addTask()}
disabled={loading}
/>
<button onClick={addTask} disabled={loading || !newTask.trim()}>
{loading ? 'π€ Thinking...' : 'Add Task'}
</button>
</div>
{error && <div className="error">{error}</div>}
<TaskList
tasks={tasks}
onToggle={toggleTask}
onDelete={deleteTask}
/>
<AIInsights tasks={tasks} />
</main>
</div>
);
}
export default App;
Step 3: OpenAI Integration
Create src/aiService.js to handle our AI logic:
import axios from 'axios';
const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';
const API_KEY = process.env.REACT_APP_OPENAI_API_KEY;
const openaiClient = axios.create({
baseURL: OPENAI_API_URL,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
});
export const enhanceTaskWithAI = async (taskText) => {
const prompt = `
Analyze this task and provide category and priority:
Task: "${taskText}"
Respond with JSON only:
{
"category": "Work|Personal|Health|Shopping|Learning|Other",
"priority": "High|Medium|Low",
"reasoning": "brief explanation"
}
`;
try {
const response = await openaiClient.post('', {
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
max_tokens: 150,
temperature: 0.3
});
const aiResponse = response.data.choices[0].message.content;
return JSON.parse(aiResponse);
} catch (error) {
console.error('AI Enhancement failed:', error);
// Fallback to basic categorization
return {
category: 'Other',
priority: 'Medium',
reasoning: 'AI unavailable, using defaults'
};
}
};
export const generateTaskSuggestions = async (existingTasks) => {
const taskList = existingTasks.map(t => `- ${t.text} (${t.category})`).join('\n');
const prompt = `
Based on these existing tasks, suggest 3 helpful new tasks:
${taskList}
Respond with JSON array:
[
{"task": "task description", "category": "category", "priority": "priority"},
{"task": "task description", "category": "category", "priority": "priority"},
{"task": "task description", "category": "category", "priority": "priority"}
]
`;
try {
const response = await openaiClient.post('', {
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
max_tokens: 200,
temperature: 0.7
});
const suggestions = JSON.parse(response.data.choices[0].message.content);
return suggestions;
} catch (error) {
console.error('Suggestion generation failed:', error);
return [];
}
};
Step 4: Task Components
Create src/components/TaskList.js:
import React from 'react';
const TaskList = ({ tasks, onToggle, onDelete }) => {
const getPriorityColor = (priority) => {
switch (priority) {
case 'High': return '#ff4757';
case 'Medium': return '#ffa502';
case 'Low': return '#2ed573';
default: return '#747d8c';
}
};
const getCategoryIcon = (category) => {
const icons = {
Work: 'πΌ',
Personal: 'π ',
Health: 'π₯',
Shopping: 'π',
Learning: 'π',
Other: 'π'
};
return icons[category] || 'π';
};
return (
<div className="task-list">
{tasks.map(task => (
<div
key={task.id}
className={`task-item ${task.completed ? 'completed' : ''}`}
>
<div className="task-content">
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggle(task.id)}
/>
<span className="task-text">{task.text}</span>
</div>
<div className="task-meta">
<span className="category">
{getCategoryIcon(task.category)} {task.category}
</span>
<span
className="priority"
style={{ color: getPriorityColor(task.priority) }}
>
{task.priority}
</span>
<button
className="delete-btn"
onClick={() => onDelete(task.id)}
>
β
</button>
</div>
</div>
))}
</div>
);
};
export default TaskList;
Create src/components/AIInsights.js:
import React, { useState } from 'react';
import { generateTaskSuggestions } from '../aiService';
const AIInsights = ({ tasks }) => {
const [suggestions, setSuggestions] = useState([]);
const [loading, setLoading] = useState(false);
const getSuggestions = async () => {
if (tasks.length === 0) return;
setLoading(true);
try {
const newSuggestions = await generateTaskSuggestions(tasks);
setSuggestions(newSuggestions);
} catch (error) {
console.error('Failed to get suggestions:', error);
} finally {
setLoading(false);
}
};
const getTaskStats = () => {
const categories = {};
const priorities = {};
tasks.forEach(task => {
categories[task.category] = (categories[task.category] || 0) + 1;
priorities[task.priority] = (priorities[task.priority] || 0) + 1;
});
return { categories, priorities };
};
const { categories, priorities } = getTaskStats();
return (
<div className="ai-insights">
<h3>π AI Insights</h3>
{tasks.length > 0 && (
<div className="stats">
<div className="stat-group">
<h4>Categories</h4>
{Object.entries(categories).map(([cat, count]) => (
<span key={cat} className="stat-item">
{cat}: {count}
</span>
))}
</div>
<div className="stat-group">
<h4>Priorities</h4>
{Object.entries(priorities).map(([pri, count]) => (
<span key={pri} className="stat-item">
{pri}: {count}
</span>
))}
</div>
</div>
)}
<button
onClick={getSuggestions}
disabled={loading || tasks.length === 0}
className="suggestions-btn"
>
{loading ? 'π€ Generating...' : 'π‘ Get AI Suggestions'}
</button>
{suggestions.length > 0 && (
<div className="suggestions">
<h4>Suggested Tasks:</h4>
{suggestions.map((suggestion, index) => (
<div key={index} className="suggestion-item">
<span>{suggestion.task}</span>
<small>({suggestion.category} - {suggestion.priority})</small>
</div>
))}
</div>
)}
</div>
);
};
export default AIInsights;
Step 5: Styling
Add this CSS to src/App.css:
.App {
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto';
}
.app-header {
text-align: center;
margin-bottom: 30px;
}
.task-input {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.task-input input {
flex: 1;
padding: 12px;
border: 2px solid #e1e8ed;
border-radius: 8px;
font-size: 16px;
}
.task-input button {
padding: 12px 24px;
background: #1da1f2;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
}
.task-input button:disabled {
background: #aab8c2;
cursor: not-allowed;
}
.error {
background: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 1px solid #e1e8ed;
border-radius: 8px;
margin-bottom: 10px;
background: white;
}
.task-item.completed {
opacity: 0.6;
text-decoration: line-through;
}
.task-content {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.task-meta {
display: flex;
align-items: center;
gap: 15px;
font-size: 14px;
}
.category {
background: #f7f9fa;
padding: 4px 8px;
border-radius: 4px;
}
.priority {
font-weight: 600;
}
.delete-btn {
background: none;
border: none;
cursor: pointer;
font-size: 16px;
}
.ai-insights {
margin-top: 40px;
padding: 20px;
background: #f8f9fa;
border-radius: 12px;
}
.stats {
display: flex;
gap: 30px;
margin: 20px 0;
}
.stat-group h4 {
margin: 0 0 10px 0;
color: #14171a;
}
.stat-item {
display: inline-block;
background: white;
padding: 4px 8px;
margin: 2px;
border-radius: 4px;
font-size: 12px;
}
.suggestions-btn {
background: #17bf63;
color: white;
border: none;
padding: 12px 20px;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
}
.suggestions {
margin-top: 20px;
}
.suggestion-item {
background: white;
padding: 12px;
margin: 8px 0;
border-radius: 8px;
border-left: 4px solid #1da1f2;
}
.suggestion-item small {
color: #657786;
margin-left: 10px;
}
Step 6: Error Handling & Loading States
Update your src/aiService.js to include better error handling:
// Add this helper function
const handleAPIError = (error) => {
if (error.response?.status === 429) {
throw new Error('Rate limit exceeded. Please try again later.');
} else if (error.response?.status === 401) {
throw new Error('Invalid API key. Please check your configuration.');
} else if (error.response?.status >= 500) {
throw new Error('OpenAI service temporarily unavailable.');
} else {
throw new Error('Failed to process request. Please try again.');
}
};
// Update your enhanceTaskWithAI function
export const enhanceTaskWithAI = async (taskText) => {
// ... existing code ...
try {
const response = await openaiClient.post('', {
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
max_tokens: 150,
temperature: 0.3
});
const aiResponse = response.data.choices[0].message.content;
return JSON.parse(aiResponse);
} catch (error) {
handleAPIError(error);
}
};
Step 7: Deployment
Deploy to Netlify (free and simple):
- Build your app:
npm run build
- Install Netlify CLI:
npm install -g netlify-cli
- Deploy:
netlify deploy --prod --dir=build
- Set environment variables in Netlify dashboard:
- Go to Site settings β Environment variables
- Add
REACT_APP_OPENAI_API_KEYwith your API key
Key Takeaways
What works well with AI integration:
- Text classification and categorization
- Priority assessment based on context
- Pattern recognition in user behavior
- Generating contextual suggestions
Important considerations:
- Always implement fallbacks for API failures
- Handle rate limits gracefully
- Keep API costs in mind (use appropriate token limits)
- Never expose API keys in client-side code
Performance tips:
- Cache AI responses when possible
- Use lower temperature values (0.1-0.3) for consistent categorization
- Implement request debouncing for real-time features
Next Steps
Your AI-powered todo app is now live! Here are some enhancements to consider:
- Add user authentication and data persistence
- Implement smart scheduling suggestions
- Add voice input with speech-to-text
- Create custom categories based on user patterns
- Add deadline predictions based on task complexity
The key to successful AI integration isn't about replacing human decision-makingβit's about augmenting it with intelligent assistance that saves time and provides insights you might miss.
What AI features will you add to your next project? Share your creations in the comments below!
Want to see the complete source code? Check out the GitHub repository and try the live demo.
Tools mentioned:
Top comments (0)