👋

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

に公開

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

  • 前回作成した家計簿アプリの機能
    • 支出の金額、カテゴリ、年月日を登録できる。「新規登録」画面と呼ぶ
      • 金額はテンキーで入力できる
      • カテゴリはリストから選択できる
      • 年月日はカレンダーから選択できる
    • 登録した支出の一覧を参照できる
    • 月毎の支出の合計を参照できる
  • 今回作成する機能
    • 登録した支出を編集できる。「編集」画面と呼ぶ

登録した支出の一覧を参照する画面はこちら。「編集」を押下する

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

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

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

  • 表示

    • 画面表示された時に処理を行う
      • useEffect(() => {・・・}, [isFocused]);
    • データを取得して状態変数を更新する
      • const [expenses, setExpenses] = useState([]);
      • const data = await loadExpense(); setExpenses(data);
    • 支出一覧を画面に表示する
      • <FlatList>
        • 表示するリスト:data={expenses}
        • 要素の一意性を表す属性:keyExtractor={(item) => item.id}
    • 支出内容と「編集」ボタンを改行せずに表示する
      • 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); }
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));
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