実践練習: ToDo アプリ

ToDo アプリの機能

  • ToDo アイテムの追加
  • ToDo アイテムの一覧表示
  • 各 ToDo アイテムの完了/未完了の切り替え
  • 完了した ToDo アイテムの削除

ToDo アプリの構成

  1. ToDo リストコンポーネント (TodoList.js): ToDo アイテムの一覧を表示し、各アイテムの状態を切り替える機能を提供します。
  2. ToDo アイテム追加コンポーネント (AddTodoItem.js): 新しい ToDo アイテムを追加するフォームを提供します。
  3. ToDo アイテムコンポーネント (TodoItem.js): 個々の ToDo アイテムを表示し、完了/未完了の状態を切り替えるチェックボックスを提供します。

ステップバイステップの実装

  1. プロジェクトのセットアップ:
    SolidJS プロジェクトを初期化し、必要な依存関係をインストールします。
  2. ToDo リストコンポーネントの実装:
    ToDo リストの状態を管理し、ToDo アイテムの一覧を表示します。また、アイテムの追加と削除のロジックを含みます。
    import { createSignal, For } from 'solid-js';
    
    function TodoList() {
      const [todos, setTodos] = createSignal([]);
    
      return (
        <div>
          <h2>ToDo List</h2>
          <ul>
            <For each={todos()}>
              {(todo) => (
                <li>
                  {todo.title} - {todo.completed ? 'Completed' : 'Incomplete'}
                </li>
              )}
            </For>
          </ul>
        </div>
      );
    }
    
    export default TodoList;
    
  3. ToDo アイテム追加コンポーネントの実装:
    ユーザーが新しい ToDo アイテムを入力し、リストに追加できるフォームを提供します。フォームの送信時には、リストの状態が更新されます。
    import { createSignal } from 'solid-js';
    
    function AddTodoItem(props) {
      const [newTodo, setNewTodo] = createSignal('');
    
      const submitHandler = (e) => {
        e.preventDefault();
        if (newTodo().trim()) {
          props.onAddTodo({ title: newTodo(), completed: false });
          setNewTodo('');
        }
      };
    
      return (
        <form onSubmit={submitHandler}>
          <input
            type='text'
            value={newTodo()}
            onInput={(e) => setNewTodo(e.target.value)}
            placeholder='Add new todo'
          />
          <button type='submit'>Add</button>
        </form>
      );
    }
    
    export default AddTodoItem;
    

    このコンポーネントでは、onAddTodoという props を通じて、親コンポーネントから ToDo アイテム追加の関数を受け取ります。ユーザーがフォームを送信すると、この関数が新しい ToDo アイテムオブジェクトを引数にして呼び出されます。
  4. ToDo アイテムコンポーネントの実装:
    各 ToDo アイテムを表示し、完了/未完了の状態を切り替える機能を提供します。アイテムが完了とマークされた場合、ビジュアル的に区別がつくようにします。
    このコンポーネントでは、onToggleonDeleteという props を通じて、親コンポーネントから ToDo アイテムの完了/未完了の切り替えと削除の関数を受け取ります。チェックボックスの状態が変更されると、onToggle関数が呼び出され、Deleteボタンがクリックされると、onDelete関数が呼び出されます。
    function TodoItem(props) {
      const toggleCompleted = () => {
        props.onToggle(props.id);
      };
    
      const deleteTodo = () => {
        props.onDelete(props.id);
      };
    
      return (
        <li>
          <input
            type='checkbox'
            checked={props.completed}
            onChange={toggleCompleted}
          />
          <span
            style={{ textDecoration: props.completed ? 'line-through' : 'none' }}
          >
            {props.title}
          </span>
          <button onClick={deleteTodo}>Delete</button>
        </li>
      );
    }
    
    export default TodoItem;
    
  5. 外部 API との連携準備:
    例として、簡単な ToDo リストアイテムを返す外部 API が存在すると仮定します。この API から ToDo リストアイテムの一覧を取得し、アプリケーションに表示します。
  6. ToDo リストコンポーネントに API からデータを取得する処理の追加:
    src/components/TodoList.jsに、API からデータを取得し表示するコンポーネントを作成します。 このコンポーネントでは、createEffectを使用してコンポーネントのマウント時にfetchTodos関数を実行し、API から ToDo リストアイテムのデータを非同期に取得します。取得したデータはtodosシグナルに保存され、Forディレクティブを使用してリストアイテムとして表示されます。
    import { createEffect, createSignal, For } from 'solid-js';
    
    function TodoList() {
      const [todos, setTodos] = createSignal([]);
    
      const fetchTodos = async () => {
        try {
          const response = await fetch('https://example.com/api/todos');
          if (!response.ok) {
            throw new Error('Failed to fetch todos');
          }
          const data = await response.json();
          setTodos(data);
        } catch (error) {
          console.error('Fetch error:', error);
          // ここでエラーメッセージを表示するなどの処理を行う
        }
      };
    
      createEffect(() => {
        fetchTodos();
      });
    
      return (
        <div>
          <h2>ToDo List</h2>
          <ul>
            <For each={todos()}>
              {(todo) => (
                <li>
                  {todo.title} - {todo.completed ? 'Completed' : 'Incomplete'}
                </li>
              )}
            </For>
          </ul>
        </div>
      );
    }
    
    export default TodoList;