😊
ネイティブアプリ開発日記(6/100)登録したデータの削除(永続化)
前回はこちら
- 前回作成した家計簿アプリの機能
- 支出登録機能
- 年月日をカレンダーから選択できる
- 金額をテンキーで入力できる
- カテゴリをリストから選択できる
- 支出参照機能
- 年月毎の集計を参照できる
- 一覧を参照できる
- 支出編集機能
- 支出(1件毎)を編集できる
- 支出登録機能
- 今回作成する機能
- 支出削除機能
- 支出(1件毎)を削除できる
- 支出削除機能
支出参照画面。「編集」を押下する

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

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

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

実装面の学び1. 画面表示
- 表示
- 画面表示された時に処理を行う
- useEffect(() => {・・・}, [isFocused]);
- データを取得して状態変数を更新する
- const [expenses, setExpenses] = useState([]);
- const data = await loadExpense(); setExpenses(data);
- 支出一覧を画面に表示する
- <FlatList>
- 表示するリスト:data={expenses}
- 要素の一意性を表す属性:keyExtractor={(item) => item.id}
- <FlatList>
- 画面表示された時に処理を行う
- 「編集」ボタン押下時の処理
- 指定したIDの支出を対象にする
- const target = expenses.find((e) => e.id === 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