rn-9-2. Todoアプリに完了機能を追加

Todoアプリに完了状態の管理を追加します

React Native - 第3章: React Native実践

課題:Todoアプリに完了機能を追加しよう

前のレッスンで作ったTodoアプリに、完了機能を追加します。 完了したTodoは取り消し線を表示し、完了状態を切り替えられるようにしましょう。

やること

  1. 1.Todoにcompletedプロパティを追加(boolean)
  2. 2.完了/未完了を切り替える関数を作る
  3. 3.完了したTodoはチェックマーク(☑)と取り消し線
  4. 4.未完了のTodoはチェックボックス(□)
  5. 5.完了ボタンと削除ボタンの両方を配置

実装のポイント

Todoの追加時にcompleted: falseを含める:

const newTodo = {
  id: Date.now().toString(),
  text: text,
  completed: false,
};

完了状態を切り替える:

const toggleTodo = (id) => {
  setTodos(todos.map(todo =>
    todo.id === id 
      ? { ...todo, completed: !todo.completed }
      : todo
  ));
};

条件付きスタイル:

<Text style={[
  styles.todoText,
  item.completed && styles.completedText
]}>
  {item.completed ? '☑' : '□'} {item.text}
</Text>

ポイント:配列をmapして特定のアイテムだけを更新するパターンは、Reactでよく使われます。

期待される出力

[新しいTodo入力] [追加]

Todo リスト:
□ 買い物に行く [完了] [削除]
☑ レポートを書く [未完了] [削除]  ← 取り消し線
□ メールを返信 [完了] [削除]
import { View, Text, TextInput, Button, FlatList, TouchableOpacity, StyleSheet } from 'react-native';
import { useState } from 'react';

export default function App() {
  const [text, setText] = useState('');
  const [todos, setTodos] = useState([]);
  
  const addTodo = () => {
    if (text.trim()) {
      const newTodo = {
        id: Date.now().toString(),
        text: text,
        // ここにcompleted: falseを追加してください
        
      };
      setTodos([...todos, newTodo]);
      setText('');
    }
  };
  
  // ここにtoggleTodo関数を作成してください
  // todosをmapして、指定されたidのcompletedを反転
  
  
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Todo リスト</Text>
      
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={text}
          onChangeText={setText}
          placeholder="新しいTodoを入力"
        />
        <Button title="追加" onPress={addTodo} />
      </View>
      
      <FlatList
        data={todos}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View style={styles.todoItem}>
            {/* ここにチェックマークとテキストを表示 */}
            {/* 完了している場合は☑と取り消し線 */}
            {/* 未完了の場合は□ */}
            <Text style={[
              styles.todoText,
              // ここに条件付きスタイルを追加
            ]}>
              {/* ここに条件でチェックマークを表示 */} {item.text}
            </Text>
            
            <View style={styles.buttonContainer}>
              {/* ここに完了/未完了ボタンを追加 */}
              <TouchableOpacity 
                style={styles.toggleButton}
                onPress={() => toggleTodo(item.id)}
              >
                <Text style={styles.buttonText}>
                  {/* 完了している場合は「未完了」、そうでなければ「完了」 */}
                </Text>
              </TouchableOpacity>
              
              <TouchableOpacity 
                style={styles.deleteButton}
                onPress={() => deleteTodo(item.id)}
              >
                <Text style={styles.buttonText}>削除</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
    gap: 10,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 5,
    padding: 10,
    fontSize: 16,
  },
  todoItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    backgroundColor: '#F5F5F5',
    padding: 15,
    marginBottom: 10,
    borderRadius: 5,
    borderLeftWidth: 4,
    borderLeftColor: '#2196F3',
  },
  todoText: {
    fontSize: 16,
    flex: 1,
  },
  completedText: {
    textDecorationLine: 'line-through',
    color: '#999',
  },
  buttonContainer: {
    flexDirection: 'row',
    gap: 5,
  },
  toggleButton: {
    backgroundColor: '#4CAF50',
    padding: 8,
    borderRadius: 5,
  },
  deleteButton: {
    backgroundColor: '#FF5252',
    padding: 8,
    borderRadius: 5,
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
    fontSize: 12,
  },
});