😊

ネイティブアプリ開発日記(6/100)登録したデータの削除(永続化)

に公開

前回はこちら
https://zenn.dev/nabeken/articles/6c0021cb9fb18f

  • 前回作成した家計簿アプリの機能
    • 支出登録機能
      • 年月日をカレンダーから選択できる
      • 金額をテンキーで入力できる
      • カテゴリをリストから選択できる
    • 支出参照機能
      • 年月毎の集計を参照できる
      • 一覧を参照できる
    • 支出編集機能
      • 支出(1件毎)を編集できる
  • 今回作成する機能
    • 支出削除機能
      • 支出(1件毎)を削除できる

支出参照画面。「編集」を押下する

支出編集画面。「削除」を押下する

削除確認ポップアップ。「削除」を押下する

支出参照画面。支出が削除されている

実装面の学び1. 画面表示

  • 表示
    • 画面表示された時に処理を行う
      • useEffect(() => {・・・}, [isFocused]);
    • データを取得して状態変数を更新する
      • const [expenses, setExpenses] = useState([]);
      • const data = await loadExpense(); setExpenses(data);
    • 支出一覧を画面に表示する
      • <FlatList>
        • 表示するリスト:data={expenses}
        • 要素の一意性を表す属性:keyExtractor={(item) => item.id}
  • 「編集」ボタン押下時の処理
    • 指定したIDの支出を対象にする
      • const target = expenses.find((e) => e.id === id);
HomeScreen.js
import React, { useEffect, useState } from 'react';
import { useIsFocused } from '@react-navigation/native';
import { View, Text, FlatList, TouchableOpacity } from 'react-native';
import { loadExpense } from '../utils/storage';
(略)
const [expenses, setExpenses] = useState([]);
const isFocused = useIsFocused(); // 画面に戻ってきたときに再読み込み
(略)
useEffect(() => {
    const fetchData = async () => {
        const data = await loadExpense();
        setExpenses(data);
    };
    if (isFocused) {
        fetchData();
    }
}, [isFocused]);
(略)
const handleEdit = (id) => {
    const target = expenses.find((e) => e.id === id);
    navigation.navigate('AddExpense', {expense: target});
};
(略)
<FlatList
    data={expenses}
    keyExtractor={(item) => item.id}
    renderItem={({ item }) => {
        return (
            <View>
                <Text>{item.date} - {item.category}: ¥{item.amount}</Text>
                <TouchableOpacity onPress={() => handleEdit(item.id)}>
                    <Text>編集</Text>
                </TouchableOpacity>
            </View>
        );
    }}
/>

実装面の学び2. 一つの画面ファイルを2つの画面で併用する

  • 編集用途と登録用途で使い分ける
    • 用途を区別する
      • const isEdit = !!route.params?.expense;
    • 「編集」用途は「削除」ボタンを表示する
      • {isEdit && ( <Button title='削除' onPress={handleDelete} /> )}
  • 削除を実行する
    • 削除実行前にポップアップで確認する
      • Alert.alert()
    • 削除実行後に支出参照画面に戻る
      • onPress: async() => {await deleteExpense(newExpense); navigation.goBack();}
AddExpenseScreen.js
import { View, Button, Alert } from 'react-native';
import { deleteExpense } from '../utils/storage';
(略)
export default function AddExpenseScreen({ route, navigation }) {
    const isEdit = !!route.params?.expense; // パラメータを引き継いだ場合は編集用途
    const handleDelete = () => {
        const newExpense = {
            id: isEdit ? route.params.expense.id :  Date.now().toString(),
            amount: parseInt(amount),
            category,
            date,
        };
        Alert.alert(
            '確認', // タイトル
            'この支出を削除しますか', // 本文
            [
                {
                    text: 'キャンセル',
                    style: 'cancel',
                },
                {
                    text: '削除',
                    onPress: async() => {
                        await deleteExpense(newExpense);
                        navigation.goBack(); 
                    }
                }
            ],
        );
    };
    (略)
    return (
        <View>
            {isEdit && (
                <Button title='削除' onPress={handleDelete} />
            )}
        </View>
    );
}

実装面の学び3. データの永続化(ストレージ上書き)

  • ストレージのデータ取得
    • const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
  • データの上書き
    • const newExpense = currentExpense.filter(item => item.id !== deletedItem.id);
    • await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = 'expenses';
(略)
export const deleteExpense = async (deletedItem) => {
    try {
        const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
        const currentExpense = jsonValue != null ? JSON.parse(jsonValue) :[];
        const newExpense = currentExpense.filter(item => item.id !== deletedItem.id);
        await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
    } catch (e) {
        console.error('削除エラー:', e);
    }
}

Discussion