11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事はNTTドコモソリューションズ Advent Calendar 2025の17日目の記事です。

こんにちは、NTTドコモソリューションズの渡邉健太です。
先月から開発エンジニアに転向しました。
何か作ってみたいと思い、スマホアプリ開発を始めたので、学びを共有します。

サマリ

身近な題材「家計簿」でReact Nativeアプリ開発に挑戦!
初心者でも楽しめる学習記録を紹介します。
※サマリはAIが生成しました

はじめに

スマホアプリ開発を学習したいと思い、アプリを一つ作ってみたので、学びをまとめました。
iOS・Android両方に対応できるように、フレームワークは「React Native」を選択しました。
(本記事では、React NativeのOSSライブラリを利用したソースコードを紹介しています。ライブラリに関する詳細は公式リポジトリをご参照ください。)
アプリ開発の学習目的のため、題材は身近なものとし、「家計簿アプリ」にしました。

家計簿アプリの概要はこちら

  • 一覧・編集メニュー
    • 支出・収入の一覧を確認できる
    • 支出・収入の年月別集計をグラフで確認できる
    • 支出・収入を更新・削除できる
  • 登録メニュー
    • 支出・収入の年月日・金額・カテゴリーを登録できる

作りたい画面・機能に対して、どのような実装・ライブラリ活用をしたかまとめる。

目次

  1. 画面切替
    1-1. 機能(例.登録、一覧・編集)の画面切替
    1-2. 分類(例. 支出、収入)の画面切替

  2. 登録機能
    2-1. 年月日の選択
    2-2. 数値の入力
    2-3. カテゴリーの選択
    2-4. ストレージ書き込み

  3. 一覧機能
    3-1. 収支一覧の参照
    3-2. 収支の年月別の集計・可視化

  4. 編集機能
    4-1. 編集対象の選択
    4-2. 更新
    4-2-1. 更新の画面操作
    4-2-2. 更新のストレージ書き込み
    4-3. 削除
    4-3-1. 削除の画面操作
    4-3-2. 削除のストレージ書き込み

詳細

1. 画面切替

1-1. 機能(例.登録、一覧・編集)の画面切替

画面下部の「ボトム・タブ・ナビゲータ」で選択した画面に切り替える。

画面切替の実装は下記の通り

  • ボトム・タブ・ナビゲータcreateBottomTabNavigator
    • 画面に表示する名前:name="登録"
    • 画面に表示する要素:component={・・・}
    • 画面に表示するアイコン:tabBarIcon: (・・・)
App.js
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator initialRouteName='登録'>
        <Tab.Screen
          name="登録"
          component={・・・}
          options={{
            tabBarIcon: ({ color, size }) => (
              <Ionicons name="pencil-outline" color={color} size={size} />
            )
          }}
        />
        <Tab.Screen 
          name='一覧・編集' 
          component={・・・}
          options={{
            tabBarIcon: ({ color, size }) => (
              <Ionicons name="eye" color={color} size={size} />
            )
          }}
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

1-2. 分類(例. 支出、収入)の画面切替

画面上部の「セグメンテッド・コントロール」を選択すると画面を切り替える。

画面切替の実装は下記の通り

  • セグメンテッド・コントロールSegmentedControl
    • 表示する選択肢:values={['支出', '収入']}
    • 選択時の処理:onChange={(event) => {・・・}}
    • 選択中の値は「状態変数」useStateで管理
  • 選択に応じて表示文言を出し分け
    • 表示:<Button title = {confirmText}/>
    • 出し分け:type === 'expense' ? '支出' : '収入'
screens/InputScreen.js
import React, { useState } from 'react';
import SegmentedControl from '@react-native-segmented-control/segmented-control';

export default function InputScreen({ route, navigation }) {

    const [type, setType] = useState('expense');
    const typeText = type === 'expense' ? '支出' : '収入';
    const confirmText = `${typeText}を入力する`

    return (
        <View>
            <SegmentedControl
                values={['支出', '収入']}
                selectedIndex={type === 'expense' ? 0 : 1 }
                onChange={(event) => {
                    const index = event.nativeEvent.selectedSegmentIndex;
                    setType(index === 0 ? 'expense' : 'income');
                }}
            />
            <Button title = {confirmText} onPress={handleSave} />
        </View>
    );
}
2. 登録機能

2-1. 年月日の選択

年月日を選択するとカレンダーが表示され、値を変更できる

日付登録の実装は下記の通り

  • カレンダーを表示
    • デートタイムピッカーDateTimePicker
  • 選択した日付を表示
    • 状態変数useStateを使用
      • 初期値は現在の日付: new Date()
      • 選択した日付に更新:onChange={・・・}
  • OKを押下するとカレンダーを非表示にする
    • 条件付きレンダリング:{条件 && (表示内容)}
    • 押下時に表示フラグを切替: <TouchableOpacity onPress={・・・}>
screens/InputScreen.js
import { View, Text, Platform, TouchableOpacity } from 'react-native';
import  DateTimePicker from '@react-native-community/datetimepicker';
import { format } from 'date-fns'; 

export default function InputScreen({ route, navigation }) {

    const [date, setDate] = useState(new Date());
    const [showPicker, setShowPicker] = useState(false);
    const displayDate = format(date, 'yyyy-MM-dd');

    return (
        <View style={styles.container}>
            <TouchableOpacity onPress={() => setShowPicker(true)}>
                <Text>{displayDate}</Text>
            </TouchableOpacity>
            {showPicker && (
                <View>
                    <DateTimePicker
                        value={date}
                        mode="date"
                        display='spinner'
                        onChange={(event, selectedDate) => {
                            setDate(selectedDate);
                        }}
                    />
                    {Platform.OS === 'ios' && (
                        <TouchableOpacity onPress={() => setShowPicker(false)}>
                            <Text>OK</Text>
                        </TouchableOpacity>
                    )}
                </View>
            )}
        </View>
    );
}

2-2. 数値の入力

金額を選択するとテンキーが表示され、入力できる。

数値入力の実装は下記の通り

  • OKで確定した金額を表示
    • 状態変数useStateを使用
  • 数値の入力
    • テンキーを直接定義
      • {['1','2', '3', '4', '5', '6', '7', '8', '9', '0'].map((digit) => (・・・)
      • 1行に収まらない分は改行する
        • pad: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center' },
    • 押した数値を追加:(digit) => {setAmount((prev) => prev + digit)}
  • 下一桁の削除
    • setAmount((prev) => prev.slice(0, -1))
  • 数値を確定し、テンキーを非表示にする
    • <AmountInputPad onConfirm={(value) => {・・・}} />
    • ただし、onConfirmは、呼び出し先AmountInputPadに渡すコールバック関数
screens/InputScreen.js
import React, { useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import AmountInputPad from '../utils/numberInput';

export default function InputScreen({ route, navigation }) {
    const [amount, setAmount] = useState('0');
    const [showAmountPad, setShowAmountPad] = useState(false);

    return (
        <View>
            <TouchableOpacity onPress={() => setShowAmountPad(true)}>
                <Text>{amount} </Text>
            </TouchableOpacity>
            {showAmountPad && (
                <AmountInputPad onConfirm={(value) => {
                    setAmount(value);
                    setShowAmountPad(false);
                }} />
            )}
        </View>
    );
}
utils/numberInput.js
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

export default function AmountInputPad({ onConfirm }) {
    const [amount, setAmount] = useState(''); 

    // 数字ボタン(0〜9)が押されたとき、金額の文字列にその数字を追加する。最大9桁とした
    const handlePress = (digit) => {
        if (amount.length < 9) {
            setAmount((prev) => prev + digit);
        }
    };

    // 削除ボタンが押された時、一番右の一桁を削除する
    const handleDelete = () => {
        setAmount((prev) => prev.slice(0, -1));
    };

   // onConfirm は親(呼び出し元)で指定するコールバック関数
    return (
        <View>
          <Text>¥{amount || '0'}</Text>
          <View style={styles.pad}>
            {['1','2', '3', '4', '5', '6', '7', '8', '9', '0'].map((digit) => (
                <TouchableOpacity key={digit} onPress={() => handlePress(digit)}>
                    <Text>{digit}</Text>
                </TouchableOpacity>
            ))}

            <TouchableOpacity onPress={handleDelete}>
                <Text></Text>
            </TouchableOpacity>

            <TouchableOpacity onPress={() => onConfirm(Number(amount))}>
                <Text>OK</Text>
            </TouchableOpacity>
          </View>
        </View>
    );
}

const styles= StyleSheet.create({
    pad: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center' },
});

2-3. カテゴリーの選択

カテゴリーを選択すると「ピッカー」が表示され、変更できる

カテゴリー登録の実装は下記の通り

  • 選択中のカテゴリーを表示
    • 状態変数useStateを使用
  • ピッカー
    • <Picker>を使用
      • 表示する選択肢: selectedValue={・・・}
      • 選択時の処理:onValueChange={・・・}
  • ピッカーを非表示にする
    • <TouchableOpacity onPress={・・・}>
screens/InputScreen.js
import React, { useState } from 'react';
import { View } from 'react-native';
import { Picker } from '@react-native-picker/picker';

export default function InputScreen({ route, navigation }) {

    const [category, setCategory] = useState('-');
    const [showCategoryPicker, setShowCategoryPicker] = useState(false); 
    const categories = ・・・;

    return (
        <TouchableOpacity onPress={() => setShowCategoryPicker(true)}>
            <Text>{category}</Text>
        </TouchableOpacity>

        {showCategoryPicker && (
            <View>
                <Picker
                    selectedValue={category}
                    onValueChange={(itemValue) => setCategory(itemValue)}
                >
                    {categories.map((cat) => (
                        <Picker.Item key={cat.value} label={cat.label} value={cat.value} />
                ))}
                </Picker>
                <TouchableOpacity onPress={() => setShowCategoryPicker(false)}>
                    <Text>OK</Text>
                </TouchableOpacity>
            </View>
        )}
    );
}

2-4. ストレージ書き込み

画面には表示されない内部処理の実装は下記の通り

  • 入力したデータを保存
    • ID付与:id: Date.now().toString(),
    • ストレージ保存関数:saveExpense(newExpense);
    • 入力用の変数の初期化関数:initializeInput();
  • ストレージ保存関数を定義
    • 現行情報を取得:AsyncStorage.getItem(STORAGE_KEY);
    • 登録情報を追加:[...currentExpense, transaction];
    • 上書き:AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
  • 入力用の変数の初期化関数を定義
    • 金額を初期化:setAmount('0');
    • カテゴリーを初期化:setCategory('-');
    • 日付を初期化:setDate(new Date());
screens/InputScreen.js
import { saveExpense } from '../utils/storage';

export default function InputScreen({ route, navigation }) {

    const [type, setType] = useState('expense');
    const [amount, setAmount] = useState('0');
    const [category, setCategory] = useState('-');
    const [date, setDate] = useState(new Date()); 

    const handleSave = async () => {
        const newExpense = {
            id: Date.now().toString(),
            type,
            amount: parseInt(amount),
            category,
            date,
        };

        const initializeInput = () => {
            setAmount('0');
            setCategory('-');
            setDate(new Date());
        };

        await saveExpense(newExpense);
        navigation.navigate('一覧・編集');
        initializeInput();
    };
}
utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = ・・・;

export const saveExpense = async(transaction) => {
    try {
        const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
        const currentExpense = jsonValue != null ? JSON.parse(jsonValue) :[];
        const newExpense = [...currentExpense, transaction];
        await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
    } catch (e) {
        console.error('保存エラー:', e);
    }
};
3. 一覧機能

3-1. 収支一覧の参照

2で保存したデータを参照できる

登録した値を参照する実装は下記の通り

  • 「一覧・編集」配下の、画面名が表示される
    • 画面管理はcreateNativeStackNavigatorで実施
    • 画面に表示する名前:name="一覧"
    • 画面に表示する要素:component={・・・}
  • 一覧を参照
    • 処理のトリガー
      • 「トリガー」が更新された際に「処理」する:useEffect(() => {処理}, [トリガー]);
      • 「トリガー」として画面表示をuseIsFocusedで確認する
    • ストレージから読み込み
      • 2と同様にAsyncStorage.getItem(STORAGE_KEY);で取得
    • 変数に格納
      • 2と同様にuseStateを使用
    • 変数に格納された値を画面に表示
      • リスト形式:<FlatList>を使用
        • リストに入れる値:data={・・・}
        • 表示処理:renderItem={({ item }) => {・・・}}
App.js
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

function ReportStack() {
  return (
      <Stack.Navigator>
        <Stack.Screen name='一覧' component={・・・} />
        <Stack.Screen name='編集' component={・・・} />
      </Stack.Navigator>
  );
}
screens/SummaryScreen.js
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList } from 'react-native';
import { useIsFocused } from '@react-navigation/native';
import { loadExpense } from '../utils/storage';

export default function SummaryScreen({ navigation }){
    const [transactions, setTransactions] = useState([]);
    const [type, setType] = useState('expense');
    const isFocused = useIsFocused();

    useEffect(() => {
        const fetchData = async () => {
            const data = await loadExpense();
            const filtered = data.filter((item) => item.type === type);
            setTransactions(filtered);
        };
        if (isFocused) {
            fetchData();
        }
    }, [isFocused, type]);

    return (
        <View>
            <FlatList
              data={transactions}
              keyExtractor={(item) => item.id}
              renderItem={({ item }) => {
                return (
                    <View>
                        <Text>{item.date} - {item.category}: ¥{item.amount}</Text>
                    </View>
                );
              }}
            />
        </View>
    )
}
utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = ・・・;

export const loadExpense = async () => {
    try {
        const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
        return jsonValue != null ? JSON.parse(jsonValue) : [];
    } catch (e) {
        console.error('読み込みエラー:', e);
        return [];
    }
};

3-2. 収支の年月別の集計・可視化

収支の年月別の合計を棒グラフで参照できる

値を集計・可視化する実装は下記の通り

  • 年月別の集計・可視化
    • 集計前の値を変数に格納
      • 3.1の通り状態変数useStateを使用
    • 集計処理
      • calculateMonthlyTotals()として、配列の要素の和を取る処理を定義
    • 集計結果を変数に格納
      • 3.1と同様に状態変数useStateを使用
      • 集計結果を格納した変数monthlyTotalsの例
        • {"2025-09": {"expense": 1500, "income": 0}, "2025-10": {"expense": 1100, "income": 0}, "2025-11": {"expense": 700, "income": 0}}
    • 変数に格納した値をグラフで表示
      • 棒グラフ:<BarChart>
      • x軸
        • labels: Object.keys(monthlyTotals),
        • 抽出結果は ["2025-09", "2025-10", "2025-11"]
      • y軸
        • data: Object.values(monthlyTotals).map(item => item[type]),
        • 抽出結果は typeexpenseの場合、[1500, 1100, 700]
  • y軸は0始まり
    • オプションでfromZero={true}を使用
  • グラフに値を表示
    • オプションでshowValuesOnTopOfBars={true}を使用
screens/SummaryScreen.js
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, Dimensions } from 'react-native';
import { useIsFocused } from '@react-navigation/native';
import { BarChart } from 'react-native-chart-kit';
import { loadExpense } from '../utils/storage';
import { calculateMonthlyTotals } from '../utils/aggregate';

export default function SummaryScreen({ navigation }){
    const [transactions, setTransactions] = useState([]);
    const [monthlyTotals, setMonthlyTotals] = useState([]);
    const [type, setType] = useState('expense');
    const isFocused = useIsFocused();

    useEffect(() => {
        const fetchData = async () => {
            const data = await loadExpense();
            const filtered = data.filter((item) => item.type === type);
            setTransactions(filtered);
            setMonthlyTotals(calculateMonthlyTotals(filtered));
        };
        if (isFocused) {
            fetchData();
        }
    }, [isFocused, type]);

    const screenWidth = Dimensions.get("window").width;

    return (
        <View>
            <BarChart
                data={{
                    labels: Object.keys(monthlyTotals),
                    datasets: [{
                        data: Object.values(monthlyTotals).map(item => item[type]),
                    },],
                }}
                width={screenWidth - 32}
                height={220}
                fromZero={true} 
                showValuesOnTopOfBars={true}
                chartConfig={{・・・}}
            />
        </View>
    )
}
utils/aggregate.js
export const calculateMonthlyTotals = (transactions) => {
    const totals = {};

    transactions.forEach((transaction) => {
        const date = new Date(transaction.date);
        const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;

        if (!totals[monthKey]) {
            totals[monthKey] = { expense:0, income:0};
        }

        if (transaction.type === 'expense') {
            totals[monthKey].expense += transaction.amount;
        } else {
            totals[monthKey].income += transaction.amount;
        }
    });

    return totals;
};
4. 編集機能

4-1. 編集対象の選択

編集対象を選択する

編集対象を選択時の実装は下記の通り

  • 編集対象を選択
    • 要素押下時の処理:<TouchableOpacity onPress={・・・}>
    • 処理の中身
      • 要素の抽出:transactions.find((e) => e.id === id)
      • 編集画面へ遷移:navigation.navigate('編集', 引数);
screens/SummaryScreen.js
import { View, Text, FlatList, TouchableOpacity } from 'react-native';
import React, { useState } from 'react';

export default function SummaryScreen({ navigation }){
    const [transactions, setTransactions] = useState([]);

    const handleEdit = (id) => {
        const target = transactions.find((e) => e.id === id);
        navigation.navigate('編集', {transaction: target});
    };

    return (
        <View>
            <FlatList
              data={transactions}
              keyExtractor={(item) => item.id}
              renderItem={({ item }) => {
                return (
                    <View>
                        <TouchableOpacity onPress={() => handleEdit(item.id)}>
                            <Text>{item.date} - {item.category}: ¥{item.amount}</Text>
                        </TouchableOpacity>
                    </View>
                );
              }}
              />
        </View>
    )
}

選択した対象を編集する

編集時の実装は下記の通り

  • 前画面に戻ることができる
    • 3に記載の通り、画面遷移はcreateNativeStackNavigatorを使用している
    • createNativeStackNavigatorは「スタック」構造で画面遷移の履歴を保持しており、1つずつ前画面に戻ることができる
  • 前画面で選択した項目の値が表示される
    • 前画面から引数を受け取る:function 関数({ route }) {・・・}
    • 引数から指定のプロパティを取得:route.params?.プロパティ
      • 指定のプロパティがある場合:値を取得
      • 指定のプロパティがない場合:undefinedを取得
    • プロパティ有無を真偽値で表現:!!route.params?.プロパティ
    • 状態変数useStateに設定:useState(真偽値 ? route.params.プロパティ.子プロパティ)
screens/InputScreen.js
import React, { useState } from 'react';

export default function InputScreen({ route, navigation }) {
    const isEdit = !!route.params?.transaction;

    const [amount, setAmount] = useState(isEdit ? String(route.params.transaction.amount) : '0');
    const [category, setCategory] = useState(isEdit ? route.params.transaction.category : '-');
    const [date, setDate] = useState(isEdit ? new Date(route.params.transaction.date) : new Date()); 
    const [type, setType] = useState(isEdit ? route.params.transaction.type : 'expense');

    return (・・・);
}

4-2. 更新

4-2-1. 更新の画面操作

2と同様の手順で値を入力し、「支出を上書きする」を押下する

更新の実装は下記の通り

  • 上書き
    • ボタン<Button/>の使用
      • 文言:title={confirmText}
      • 押下時の処理:onPress={・・・}
screens/InputScreen.js
import { updateExpense } from '../utils/storage';

export default function InputScreen({ route, navigation }) {
    const isEdit = !!route.params?.transaction;

    const [amount, setAmount] = useState(isEdit ? String(route.params.transaction.amount) : '0');
    const [category, setCategory] = useState(isEdit ? route.params.transaction.category : '-');
    const [date, setDate] = useState(isEdit ? new Date(route.params.transaction.date) : new Date()); 
    const [type, setType] = useState(isEdit ? route.params.transaction.type : 'expense');

    const handleSave = async () => {
        const newExpense = {
            id: isEdit ? route.params.transaction.id :  Date.now().toString(),
            type,
            amount: parseInt(amount),
            category,
            date,
        };
        if (isEdit) {
            await updateExpense(newExpense);
            navigation.goBack(); 
        } 
    };

    const typeText = type === 'expense' ? '支出' : '収入';
    const confirmText = isEdit 
        ? `${typeText}を上書きする`
        : `${typeText}を入力する`;

    return (
        <View>
            <Button title = {confirmText} onPress={handleSave} />
        </View>
    );
}

4-2-2. 更新のストレージ書き込み

画面には表示されない内部処理の実装は下記の通り

  • ストレージの現行情報を取得:AsyncStorage.getItem(STORAGE_KEY);
  • 指定したIDの要素を更新:currentExpense.map(item => item.id === updatedItem.id ? updatedItem : item);
  • 上書き:AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = ・・・;

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);
    }
};

4-3. 削除

4-3-1. 削除の画面操作

編集画面から削除を選択する

削除押下時の実装は下記の通り

  • 削除する
    • ボタン<Button/>の使用
      • 文言:title='削除'
      • 押下時の処理:onPress={・・・}
screens/InputScreen.js
import { View, Text, Button } from 'react-native';

export default function InputScreen({ route, navigation }) {
    const isEdit = !!route.params?.transaction;
    const handleDelete = () => {・・・}
    return (
        <View>
            {isEdit && (
                <Button title='削除' onPress={handleDelete} />
            )}
        </View>
    );
}

確認ダイアログを表示する

  • 確認ダイアログ
    • Alert.alertを使用
      • タイトル:'確認'
      • 本文:'この支出を削除しますか'
      • キャンセル:text: 'キャンセル', style: 'cancel',
      • 実行
        • 文言:text: '削除'
        • 押下時の処理:onPress: async() => {・・・}
screens/InputScreen.js
import { Alert } from 'react-native';
import { deleteExpense } from '../utils/storage';

export default function InputScreen({ route, navigation }) {

    const handleDelete = () => {
        const newExpense = {
            id: isEdit ? route.params.transaction.id :  Date.now().toString(),
            type,
            amount: parseInt(amount),
            category,
            date,
        };
        Alert.alert(
            '確認', // タイトル
            'この支出を削除しますか', // 本文
            [
                {
                    text: 'キャンセル',
                    style: 'cancel',
                },
                {
                    text: '削除',
                    onPress: async() => {
                        await deleteExpense(newExpense);
                        navigation.goBack(); 
                    }
                }
            ],
        );
    };

    return (・・・);
}

4-3-2. 削除のストレージ書き込み

画面には表示されない内部処理の実装は下記の通り

  • ストレージの現行情報を取得
    • AsyncStorage.getItem(STORAGE_KEY);
  • 指定したIDの要素を除く
    • currentExpense.filter(item => item.id !== deletedItem.id);
  • ストレージを上書き
    • AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newExpense));
utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = ・・・;

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);
    }
}

所感

Copilotが隣にいれば
・やりたいことに適したライブラリを見つける
・見つけたライブラリの使い方を確認する
をスムーズにできるため、
初心者でもスマホアプリを開発できることが分かった。

記載されている会社名、製品名、サービス名は各社の商標または登録商標です。

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?