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

支出を編集する画面はこちら。
「編集」画面は「新規登録」画面と同様の形式。
ただし、年月日、金額、カテゴリは「編集」画面では編集前の値を受け継ぐ。

編集後の画面はこちら。
支出の一覧を参照する画面で、編集後の値になった。

実装面の学び1. 画面表示
-
表示
- 画面表示された時に処理を行う
- useEffect(() => {・・・}, [isFocused]);
- データを取得して状態変数を更新する
- const [expenses, setExpenses] = useState([]);
- const data = await loadExpense(); setExpenses(data);
- 支出一覧を画面に表示する
- <FlatList>
- 表示するリスト:data={expenses}
- 要素の一意性を表す属性:keyExtractor={(item) => item.id}
- <FlatList>
- 支出内容と「編集」ボタンを改行せずに表示する
- styles= StyleSheet.create({row: {flexDirection: 'row', gap: 20}});
- 画面表示された時に処理を行う
-
「編集」ボタン押下時の処理
- 要素を押した時に処理をする
- <TouchableOpacity onPress={() => handleEdit(item.id)}>
- 支出一覧の中から指定したIDの要素を取得する
- const target = expenses.find((e) => e.id === id);
- 「新規登録」画面を流用する
- navigation.navigate('AddExpense', {expenses: target});
- 要素を押した時に処理をする
HomeScreen.js
import React, { useEffect, useState } from 'react';
import { useIsFocused } from '@react-navigation/native';
import { View, Text, FlatList, StyleSheet, 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', {expenses: target});
// AddExpense では route.params.expense としてtargetを受け取る
};
(略)
<FlatList
data={expenses}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return (
<View style={styles.row}>
<Text>{item.date} - {item.category}: ¥{item.amount}</Text>
<TouchableOpacity onPress={() => handleEdit(item.id)}>
<Text>編集</Text>
</TouchableOpacity>
</View>
);
}}
/>
(略)
const styles= StyleSheet.create({
row: {flexDirection: 'row', gap: 20},
});
実装面の学び2. 一つの画面ファイルを2つの画面で併用する
- 2つの用途のどちらを実施中なのか区別する
- const isEdit = !!route.params?.expense;
- 「編集」用途の場合は、「登録」用途と異なる処理を行う
- 既存の値を上書きするためにIDを引き継ぐ
- id: isEdit ? route.params.expense.id : Date.now().toString(),
- ストレージ上書きのモジュールを呼び出す
- if (isEdit) { await updateExpense(newExpense); }
- 既存の値を上書きするためにIDを引き継ぐ
AddExpenseScreen.js
import { saveExpense, updateExpense } from '../utils/storage';
(略)
export default function AddExpenseScreen({ route, navigation }) {
const isEdit = !!route.params?.expense;
(略)
const handleSave = async () => {
const newExpense = {
id: isEdit ? route.params.expense.id : Date.now().toString(),
amount,
category,
date,
};
if (isEdit) { await updateExpense(newExpense); }
};
(略)
return (
<View>
<Button title='保存' onPress={handleSave} />
</View>
);
}
実装面の学び3. データの永続化(ストレージ上書き)
- ストレージから現行データを取得する
- データ取得
- const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
- 文字列をJSONに変換する
- const currentExpense = jsonValue != null ? JSON.parse(jsonValue) :[];
- データ取得
- 編集してストレージに書き込む
- 指定したIDの要素を上書きする
- const newExpense = currentExpense.map(item => item.id === updatedItem.id ? updatedItem : item);
- 上書きした内容でストレージに書き込む
- await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
- 指定したIDの要素を上書きする
utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = 'expenses';
(略)
export const updateExpense = async(updatedItem) => {
try {
const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
const currentExpense = jsonValue != null ? JSON.parse(jsonValue) :[];
const newExpense = currentExpense.map(item =>
item.id === updatedItem.id ? updatedItem : item
);
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
} catch (e) {
console.error('更新エラー:', e);
}
};
Discussion