LoginSignup
0
0

自作ローカルAPIを用いて、ReactNativeでTODOアプリを作ってみた

Posted at

はじめに

前回、TODOアプリのAPIを作成して、ローカルの挙動確認まで実施しました。
その続きとして、ReactNativeでTODOアプリを作成して、APIを叩く実装をします。

以下は、前回の記事のリンクです。

内容

まず最初に、前回のAPIの修正が必要になります。

追加実装した内容

  • idをすべてUUIDに変更
  • ローカルサーバーAPIを叩く際のポート番号と接続の権限周りの変更

idをすべてUUIDに変更

変更する理由は、numberで管理していると、数字の重複の可能性があるためです。

uuidライブラリーをインストール

 npm install uuid

以下のコードに修正。

App.jsの変更後のコード
const express = require('express');
const { v4: uuidv4 } = require('uuid'); // uuidライブラリからv4メソッドをインポート
const app = express();
const port = 3000;

// CORS設定
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8081'); // 許可するオリジン
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

// To-Doリストの仮データ
const todos = [
  { id: uuidv4(), title: 'タスク1', completed: false },
  { id: uuidv4(), title: 'タスク2', completed: true },
];

// JSONデータのパースを有効にする
app.use(express.json());

// To-Doリストの取得
app.get('/todos', (req, res) => {
  res.json(todos);
});

// To-Doリストの作成
app.post('/todos', (req, res) => {
  const newTodo = req.body;
  newTodo.id = uuidv4(); // 新しいToDoのidにUUIDを設定
  todos.push(newTodo);
  res.status(201).json(newTodo);
});

// To-Doリストの更新
app.put('/todos/:id', (req, res) => {
  const todoId = req.params.id; // UUIDをそのまま使用
  const updatedTodo = req.body;
  const todoIndex = todos.findIndex(todo => todo.id === todoId);
  if (todoIndex !== -1) {
    todos[todoIndex] = { ...todos[todoIndex], ...updatedTodo };
    res.json(todos[todoIndex]);
  } else {
    res.status(404).json({ message: '指定されたToDoが見つかりません' });
  }
});

// To-Doリストの削除
app.delete('/todos/:id', (req, res) => {
  const todoId = req.params.id; // UUIDをそのまま使用
  const todoIndex = todos.findIndex(todo => todo.id === todoId);
  if (todoIndex !== -1) {
    const deletedTodo = todos.splice(todoIndex, 1)[0];
    res.json(deletedTodo);
  } else {
    res.status(404).json({ message: '指定されたToDoが見つかりません' });
  }
});

// サーバーを起動
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});


ローカルサーバーAPIを叩く際のポート番号と接続の権限周りの変更

CORSエラーより、以下のコードを追加しました。

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8081'); // 許可するオリジン
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

詳細の説明は以下のリンクが参考になりました。

API通信をおこなうためのモジュールを作成

axios(API通信を行うため)をインストール

npm install axios

todoapi.jsの作成

APIを叩くためのクラスを作成すために、todoapi.jsファイルを作成します。

import axios from 'axios';

const API_URL = 'http://localhost:3000';

// TODOリストを取得する関数
export const fetchTodos = async () => {
  try {
    const response = await axios.get(`${API_URL}/todos`);
    return response.data;
  } catch (error) {
    throw error;
  }
}

// 新しいTODOを作成する関数
export const createTodo = async (newTodoData) => {
  try {
    const response = await axios.post(`${API_URL}/todos`, newTodoData);
    return response.data;
  } catch (error) {
    throw error;
  }
}

// TODOを更新する関数
export const updateTodo = async (todoId, updatedTodoData) => {
  try {
    const response = await axios.put(`${API_URL}/todos/${todoId}`, updatedTodoData);
    return response.data;
  } catch (error) {
    throw error;
  }
}

// TODOを削除する関数
export const deleteTodo = async (todoId) => {
  try {
    const response = await axios.delete(`${API_URL}/todos/${todoId}`);
    return response.data;
  } catch (error) {
    throw error;
  }
}
  • 上記の関数は全て非同期関数(async await)で定義されており、TODOリストの取得GET 追加POST 更新PUT 削除DELETE を実装しています
  • trycatch は、JavaScriptにおいてエラーハンドリングに使用される構文です。API通信が失敗した際にエラーを取得し、throwで投げることができます
  • Axiosライブラリを使用して、指定されたAPIのURLからToDoリストのデータを非同期通信でCRUD処理を行っています

GET

const response = await axios.get(${API_URL}/todos);

POST

const response = await axios.post(`${API_URL}/todos`, newTodoData);

PUT

const response = await axios.put(`${API_URL}/todos/${todoId}`, updatedTodoData);

DELETE

 const response = await axios.delete(`${API_URL}/todos/${todoId}`);

TODOアプリのメイン画面の作成

App.jsの作成し、実装していきます。

変数を定義

TODOリストと新規作成したTODOを格納するための変数を定義します。

  const [todos, setTodos] = useState([]);
  const [newTodoText, setNewTodoText] = useState('');

登録されているTODOリストの値を取得

 useEffect(() => {
    fetchTodos()
      .then((data) => setTodos(data))
      .catch((error) => console.error('TODOリストの取得に失敗しました', error));
  }, []);

useEffectはReactのhooksの一つであり、でTODOリストの初期値を取得するために実装しています。このuseEffectは空のリストを持っているため、コンポーネントが初めてレンダリングされた直後に1回だけ呼び出されます。

ボタンが押されたときのCRUD処理を定義

const handleCreateTodo = () => {
    if (newTodoText.trim() === '') {
      return;
    }
    const newTodoData = {
      title: newTodoText,
      completed: false,
    };
    
    createTodo(newTodoData)
      .then((createdTodo) => {
        setTodos((prevTodos) => [...prevTodos, createdTodo]);
        setNewTodoText('');
      })
      .catch((error) => console.error('TODOの作成に失敗しました', error));
  };
  • 新しく追加するTODOであるnewTodoText の値が空白でないことを確認しています。もし空白の場合は、何もせずに関数から抜けます
  • 新しく作成されたTODOが createTodo 関数によって返されたら、そのTODOを setTodos を使用して現在のTODOリストに追加します
  • setNewTodoText('') を呼び出して、新しいTODOのテキスト入力フィールドをクリアします
  • createTodo 関数でエラーが発生した場合、エラーをコンソールに出力します

同様に、読み込み・更新・削除を実装していく。その辺りに関しては、サンプルコードのみ添付いたします。

サンプルコード
  const handleUpdateTodo = (todoId, updatedData) => {
    // TODOを更新し、stateを更新
    updateTodo(todoId, updatedData)
      .then((updatedTodo) => {
        setTodos((prevTodos) =>
          prevTodos.map((todo) => (todo.id === updatedTodo.id ? updatedTodo : todo))
        );
      })
      .catch((error) => console.error('TODOの更新に失敗しました', error));
  };

  const handleDeleteTodo = (todoId) => {
    // TODOを削除し、stateから削除
    deleteTodo(todoId)
      .then((deletedTodo) => {
        setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== deletedTodo.id));
      })
      .catch((error) => console.error('TODOの削除に失敗しました', error));
  };

  const handleReloadTodo = () => {
    fetchTodos()
      .then((data) => setTodos(data))
      .catch((error) => console.error('TODOリストの取得に失敗しました', error));
  };

追加・更新ボタンの追加

  return (
    <View>
      <Button title="更新" onPress={handleReloadTodo} />
      <Text>TODOリスト</Text>
      <TextInput
        placeholder="新しいTODOを入力"
        value={newTodoText}
        onChangeText={(text) => setNewTodoText(text)}
      />
      <Button title="追加" onPress={handleCreateTodo} />
    {/* 省略 */}
    </View>
  );
  • Buttonコンポーネントが2つあります。一つは更新ボタンで、handleReloadTodo関数が押されたときに呼び出されます。もう一つは追加ボタンで、handleCreateTodo関数が押されたときに呼び出されます

  • TextInputコンポーネントは新しいTODOを入力するためのテキストフィールドです。valueプロパティはnewTodoTextとバインドされており、ユーザーが入力したテキストはsetNewTodoText関数を介して更新されます

TODOのリスト表示

    <FlatList
        data={todos}
        keyExtractor={(item) => item.id.toString()}
        renderItem={({ item }) => (
          <View>
            <TouchableOpacity onPress={() => handleUpdateTodo(item.id, { completed: !item.completed })}>
              <Text style={{ textDecorationLine: item.completed ? 'line-through' : 'none' }}>
                {item.title}
              </Text>
            </TouchableOpacity>
            <Button title="削除" onPress={() => handleDeleteTodo(item.id)} />
          </View>
        )}
      />
  • FlatListコンポーネントはTODOリストの表示を行います。dataプロパティにはtodos配列が渡されており、keyExtractorプロパティは各アイテムの一意のIDを取得するための関数を指定しています

  • renderItemプロパティは各TODOアイテムの表示方法を定義します。各アイテムはTouchableOpacityでラップされており、タップされたときにhandleUpdateTodo関数が呼び出され、TODOの完了状態が切り替わります。TODOが完了している場合はテキストに取り消し線が追加されます。また、削除ボタンがあり、それを押すとhandleDeleteTodo関数が呼び出され、対応するTODOアイテムが削除されます

挙動確認

TODOのAPIをローカル環境で立ち上げ、シミュレーターで確認

API通信を用いて、Webブラウザ・シミュレーター上でTODO管理を行うことができました。

解説は以上になります。
サンプルコード添付しておきます。

App.js
import React, { useEffect, useState } from 'react';
import { View, Text, Button, TextInput, FlatList, TouchableOpacity , StyleSheet } from 'react-native';
import { fetchTodos, createTodo, updateTodo, deleteTodo } from './api/todoapi';
import 'react-native-get-random-values';


function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [newTodoText, setNewTodoText] = useState('');

  useEffect(() => {
    fetchTodos()
      .then((data) => setTodos(data))
      .catch((error) => console.error('TODOリストの取得に失敗しました', error));
  }, []);

  const handleCreateTodo = () => {
    if (newTodoText.trim() === '') {
      return;
    }
    const newTodoData = {
      title: newTodoText,
      completed: false,
    };
    createTodo(newTodoData)
      .then((createdTodo) => {
        setTodos((prevTodos) => [...prevTodos, createdTodo]);
        setNewTodoText('');
      })
      .catch((error) => console.error('TODOの作成に失敗しました', error));
  };

  const handleUpdateTodo = (todoId, updatedData) => {
    updateTodo(todoId, updatedData)
      .then((updatedTodo) => {
        setTodos((prevTodos) =>
          prevTodos.map((todo) => (todo.id === updatedTodo.id ? updatedTodo : todo))
        );
      })
      .catch((error) => console.error('TODOの更新に失敗しました', error));
  };

  const handleDeleteTodo = (todoId) => {
    deleteTodo(todoId)
      .then((deletedTodo) => {
        setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== deletedTodo.id));
      })
      .catch((error) => console.error('TODOの削除に失敗しました', error));
  };

  const handleReloadTodo = () => {
    fetchTodos()
      .then((data) => setTodos(data))
      .catch((error) => console.error('TODOリストの取得に失敗しました', error));
  };

 return (
    <View style={styles.container}>

    
      <Button title="更新" onPress={handleReloadTodo} />
      <Text style={styles.header}>TODOリスト</Text>
      <TextInput
        placeholder="新しいTODOを入力"
        value={newTodoText}
        onChangeText={(text) => setNewTodoText(text)}
        style={styles.input}
      />
      <Button title="追加" onPress={handleCreateTodo} />
      <FlatList
        data={todos}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View style={styles.todoItem}>
            <TouchableOpacity onPress={() => handleUpdateTodo(item.id, { completed: !item.completed })}>
              <Text style={[styles.todoText, item.completed ? styles.completedTodo : null]}>
                {item.title}
              </Text>
            </TouchableOpacity>
            <Button title="削除" onPress={() => handleDeleteTodo(item.id)} />
          </View>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#fff',
    marginTop: 100
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 16,
  },
  input: {
    borderWidth: 1,
    borderColor: 'gray',
    borderRadius: 4,
    padding: 8,
    marginBottom: 16,
  },
  todoItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  todoText: {
    fontSize: 18,
  },
  completedTodo: {
    textDecorationLine: 'line-through',
    color: 'gray',
  },
});

export default TodoApp;

todoapi.js
import axios from 'axios';

const API_URL = 'http://localhost:3000';

// TODOリストを取得する関数
export const fetchTodos = async () => {
  try {
    const response = await axios.get(`${API_URL}/todos`);
    return response.data;
  } catch (error) {
    throw error;
  }
}

// 新しいTODOを作成する関数
export const createTodo = async (newTodoData) => {
  try {
    const response = await axios.post(`${API_URL}/todos`, newTodoData);
    return response.data;
  } catch (error) {
    throw error;
  }
}

// TODOを更新する関数
export const updateTodo = async (todoId, updatedTodoData) => {
  try {
    const response = await axios.put(`${API_URL}/todos/${todoId}`, updatedTodoData);
    return response.data;
  } catch (error) {
    throw error;
  }
}

// TODOを削除する関数
export const deleteTodo = async (todoId) => {
  try {
    const response = await axios.delete(`${API_URL}/todos/${todoId}`);
    return response.data;
  } catch (error) {
    throw error;
  }
}


最後に

他にも間違えている点、他に良い方法などありましたら、コメントいただけますと大変うれしいです。
良かったと思ったら、いいねやTwitterのフォローよろしくお願いいたします!

個人でアプリを作成しているので、良かったら覗いてみてください

0
0
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
0
0