DEV Community

Cover image for Build Your First Todo App on ICP: A Friendly Guide
Mahmud eyitoba bello
Mahmud eyitoba bello

Posted on

Build Your First Todo App on ICP: A Friendly Guide

Ready to build something cool on the Internet Computer? Let's create a todo app together. Don't worry if you're new to this - I'll walk you through everything step by step, no fancy jargon required.

Why ICP Is Pretty Cool

Think of Internet Computer like a giant, shared computer that's always online. Instead of running your app on servers owned by big companies, it runs on a network that belongs to everyone. Pretty neat, right? Here's why it's awesome:

  • Your app never goes down (unless the internet itself breaks!)
  • You don't need to pay for expensive servers
  • Users don't pay gas fees like on other blockchains
  • It's fast - like, really fast

Getting Started

First, let's get your computer ready. Open your terminal and type:

sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"
Enter fullscreen mode Exit fullscreen mode

This installs dfx - your new best friend for building ICP apps. Now create your project:

dfx new todo_app
cd todo_app
Enter fullscreen mode Exit fullscreen mode

Building the Brain (Backend)

Let's create the backend first. find a file called src/todo_app_backend/main.mo and drop in this code:

import Array "mo:base/Array";
actor {
    // This is what each todo looks like
    type Todo = {
        id: Nat;
        text: Text;
        completed: Bool;
    };

    // Keep all todos in one place
    private var todos: [Todo] = [];
    private var nextId: Nat = 0;

    // Add a new todo to the list
    public func addTodo(text: Text) : async Nat {
        let todo: Todo = {
            id = nextId;
            text = text;
            completed = false;
        };
        todos := Array.append(todos, [todo]);
        nextId += 1;
        return todo.id;
    };

    // Get all your todos
    public query func getTodos() : async [Todo] {
        return todos;
    };

    // Mark a todo as done (or not done)
    public func toggleTodo(id: Nat) : async Bool {
        todos := Array.map(todos, func (todo: Todo) : Todo {
            if (todo.id == id) {
                return {
                    id = todo.id;
                    text = todo.text;
                    completed = not todo.completed;
                };
            };
            todo;
        });
        return true;
    };

    // Remove a todo
    public func deleteTodo(id: Nat) : async Bool {
        todos := Array.filter(todos, func(todo: Todo) : Bool { 
            todo.id != id 
        });
        return true;
    };
}
Enter fullscreen mode Exit fullscreen mode

Making It Pretty (Frontend)

Now for the part you'll actually see! Create src/todo_app_frontend/src/main.jsx:

import React, { useState, useEffect } from 'react';
import { todo_app_backend } from 'declarations/todo_app_backend';

function App() {
    const [todos, setTodos] = useState([]);
    const [newTodo, setNewTodo] = useState('');

    // Get all todos when the app starts
    const loadTodos = async () => {
        const result = await todo_app_backend.getTodos();
        setTodos(result);
    };

    // Add a new todo
    const handleAdd = async (e) => {
        e.preventDefault();
        if (!newTodo.trim()) return;
        await todo_app_backend.addTodo(newTodo);
        setNewTodo('');
        loadTodos();
    };

    // Mark a todo as done/not done
    const handleToggle = async (id) => {
        await todo_app_backend.toggleTodo(id);
        loadTodos();
    };

    // Delete a todo
    const handleDelete = async (id) => {
        await todo_app_backend.deleteTodo(id);
        loadTodos();
    };

    useEffect(() => {
        loadTodos();
    }, []);

    return (
        <div className="container mx-auto p-4">
            <h1 className="text-2xl font-bold mb-4">Todo List</h1>

            <form onSubmit={handleAdd} className="mb-4">
                <input
                    type="text"
                    value={newTodo}
                    onChange={(e) => setNewTodo(e.target.value)}
                    className="border p-2 mr-2"
                    placeholder="Add new todo"
                />
                <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
                    Add
                </button>
            </form>

            <ul>
                {todos.map((todo) => (
                    <li key={todo.id} className="flex items-center mb-2">
                        <input
                            type="checkbox"
                            checked={todo.completed}
                            onChange={() => handleToggle(todo.id)}
                            className="mr-2"
                        />
                        <span className={todo.completed ? 'line-through' : ''}>
                            {todo.text}
                        </span>
                        <button
                            onClick={() => handleDelete(todo.id)}
                            className="ml-auto text-red-500"
                        >
                            Delete
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Starting It Up

Time to see your app in action! In your terminal:

dfx start --clean --background
dfx deploy
Enter fullscreen mode Exit fullscreen mode

Your app will pop up at http://localhost:4943. Pretty cool, right?

What's Actually Happening Here?

Let's break down what we just built:

  1. The Motoko code (backend) is like a super-secure notebook:

    • It remembers all your todos
    • Gives each todo a unique ID
    • Keeps track of what's done and what isn't
    • Can add, check off, or delete todos
  2. The React code (frontend) is like a friendly interface:

    • Shows you all your todos
    • Lets you type in new ones
    • Has checkboxes to mark things as done
    • Includes delete buttons to remove todos

Cool Things You Can Add

Now that you've got the basics working, why not spice it up? You could:

  • Add due dates
  • Group todos into categories
  • Share todos with friends
  • Add file attachments

When Things Go Wrong

Don't panic! Here's what to check:

  • Is dfx running? (check with dfx status)
  • Did you save all your files?
  • Are there any red error messages?
  • When in doubt, try dfx deploy again

Need Help?

Everyone gets stuck sometimes. Here's where to find help:

  • DFINITY's Discord channel
  • Stack Overflow (tag: 'internet-computer')
  • Internet Computer forums

Remember: every developer started exactly where you are now. Take your time, have fun, and don't be afraid to experiment - that's how we learn!

Top comments (0)