はじめに
前回、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
を実装しています -
try
とcatch
は、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のフォローよろしくお願いいたします!
個人でアプリを作成しているので、良かったら覗いてみてください