# 未完Study Web開発のすゝめ第2回 補助資料 ## 資料 - API の仕様書 https://hackmd.io/@manattan/S1wbZjZ9Y - 今日の完成形 https://friendly-khorana-91cd70.netlify.app/ ## 今日の内容 - 先週のおさらい - React アプリを手元で動かしてみよう - とりあえずいい感じにいっぱい表示させてみよう - API からデータを取得してみよう - 複数の API を叩いてみよう - todoを追加する機能 - ステータスを変更する機能 ## React アプリを手元で動かしてみよう - [前回](https://hackmd.io/@manattan/SyidUyUKF#create-react-app%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86%EF%BC%81)の復習です ## とりあえずいい感じにいっぱい表示させてみよう ::: info 目標: Component の考え方を理解しましょう 方法: Header と Footer をつけてみます ::: ### 手順1: とりあえずデフォルトの表示をリセットしていく - `src/App.js`を書き換える ```javascript= import './App.css'; function App() { return ( <div>hello, world!</div> ); } export default App; ``` - `src/App.css`を空にする ### 手順2: Header を作ろう - `src/components/Header.jsx`を作成する - `src/components/Header.jsx` の中身を下のようにする ```javascript= const Header = () => { return ( <div>ここがHeaderです</div> ) } export default Header; ``` - src/App.js にて、Header を追加するよ ```javascript= import './App.css'; // Header を import する import Header from './components/Header' function App() { return ( {/* Appの中身をこんな感じにする */} <div> <Header /> <div>hello, world!</div> </div> ); } export default App; ``` - [chakra UI](https://chakra-ui.com/)という UI コンポーネントを使います - 他にも、material UI とか Bootstrap とか色々あるよ - 以下コマンドを実行してライブラリをインストールします - ``` // npm i は npm install と同じだよ 参考: https://qiita.com/standard-software/items/2ac49a409688733c90e7 > npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4 ``` - React のプロジェクト全体に UI コンポーネントを使えるように `Provider` で囲む - https://chakra-ui.com/guides/getting-started/cra-guide#2-provider-setup - `src/index.js` を以下のように書き換える - ```javascript= import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // 追加する import { ChakraProvider } from '@chakra-ui/react' ReactDOM.render( <React.StrictMode> {/* 囲んであげる */} <ChakraProvider> <App /> </ChakraProvider> </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); ``` - chakra ui を使っていい感じにしよう - `src/components/Header.jsx` をいい感じにする - ```javascript= // chakra-ui の要素を使えるように import する <- 忘れない!! import { Flex, Center, Box, Text } from '@chakra-ui/react'; const Header = () => { return ( {/* <Flex /> は display: flex; が中身に適用されているのと同じ! */} <Flex bg="red.400" h="64px"> <Center px="64px"> <Text color="white" fontWeight="bold"> ToDoアプリ </Text> </Center> <Box flex="1" /> <Center px="32px"> <Text color="white" fontWeight="bold"> ログインしてね </Text> </Center> </Flex> ); }; export default Header; ``` ### 手順3: 同様に Footer も作っちゃおう `src/components/Footer.jsx` ```javascript= import { Center } from '@chakra-ui/react'; const Footer = () => { return ( <Center bg="red.400" h="64px" w="full" position="absolute" bottom="0" color="white" fontWeight="bold" > 2021 mikan project </Center> ); }; export default Footer; ``` `src/App.js` ```javascript= import { Box } from '@chakra-ui/react'; import './App.css'; import Header from './components/Header'; import Footer from './components/Footer'; function App() { return ( <Box position="relative" minHeight="100vh" pb="64px"> <Header /> <div>hello, world!</div> <Footer /> </Box> ); } export default App; ``` ## API からデータを取得してみよう ::: info とりあえず、運営で用意したAPIを使ってデータをとってこようと思います。来週にAPIを自分たちで作ってみます! ::: ### 手順0: やること - todoリストの一覧を表示させるコンポーネントを作る - ページが読み込まれたら、APIにGETリクエストを送って、todoリストの情報を取得してくる - その情報をいい感じにコンポーネント内で表示させる ### 手順1: とりあえず component を作りましょう - `src/components/TodoList.jsx` を作成 ```javascript= import { Box } from '@chakra-ui/react'; const TodoList = () => { return ( <Box m="24px"> <Box> <Box m="16px" p="16px" bg="yellow.100" borderRadius="4px"> Lorem ipsum dolor sit amet, consectetur adipisicing elit </Box> <Box m="16px" p="16px" bg="yellow.100" borderRadius="4px"> Assumenda, quia temporibus eveniet a libero incidunt suscipit </Box> <Box m="16px" p="16px" bg="yellow.100" borderRadius="4px"> Quidem, ipsam illum quis sed voluptatum quae eum fugit earum </Box> </Box> </Box> ); }; export default TodoList; ``` - `src/App.js` で読み取る ### 手順2: API からデータを取得する関数を作る - `src/lib/index.js` を作って、API にリクエストを送る関数を入れておく ```javascript= const BaseURL = 'https://trello-server-331705-xxe5t7bbba-an.a.run.app/api'; // async 関数は Promise (予約しておく関数、的な) を return するよ。詳しくはJSの非同期処理についてチェック // fetch についてはこちら https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch export const getAllLists = async () => { const res = await fetch(`${BaseURL}/todos`, { // リクエストの種類や、headerにつけるのをここで指定したりするよ method: 'GET', }); return res.json(); }; ``` ### 手順3: 手順2で作成した関数をコンポーネント内で呼び出して実行! - `src/components/TodoList.jsx` を魔改造 ```javascript= import { useEffect, useState } from 'react'; import { Box } from '@chakra-ui/react'; import { getAllLists } from '../lib/index'; const TodoList = () => { // useState では、component 内に状態を保つことができます。0番目の要素は値を、1番目の要素が0番目の値を更新する関数になる。 const [todos, setTodos] = useState([]); const fetchLists = async () => { // ここでAPIにリクエストを送る! const res = await getAllLists(); setTodos(res); }; // useEffect は、第2引数 に配列を持たせ、その中身の値が変わったのを検知したら第1引数の関数を実行するよ。空の配列にすると、特別に 初期描画時に実行してくれます。 useEffect(() => { fetchLists(); }, []); // useState() 内の todos がtrueだったら return させるよ if (todos) { return ( <Box> {todos.map((todo) => { return ( <Box m="16px" p="16px" bg="yellow.100" borderRadius="4px" key={todo.title} > {todo.title} </Box> ); })} </Box> ); } // 何もない場合は何も表示させない return <></>; }; export default TodoList; ``` ## 複数の API を叩いてみよう 今回用意している API / 来週作る API の仕様は[こちら](https://hackmd.io/@manattan/S1wbZjZ9Y) ### 手順1: 新しい todo を追加する機能を実装 - `src/components/AddTodo.jsx`を追加 ```javascript= import { useState } from 'react'; import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, useDisclosure, Button, Center, Input, useToast, } from '@chakra-ui/react'; import { addTodoList } from '../lib/index'; const AddTodo = () => { // chakra-ui の公式docsを見てみよう const { isOpen, onOpen, onClose } = useDisclosure(); const [todo, setTodo] = useState(null); const toast = useToast(); const AddTodoToServer = async () => { const res = await addTodoList(todo); if (res.message === 'success!') { onClose(); toast({ title: '正常に追加されました', status: 'success', duration: 3000, isClosable: true, }); // return させないと下のtoastも実行されちゃう! return; } toast({ title: '追加に失敗しました', status: 'error', duration: 3000, isClosable: true, }); }; return ( <Center mt="32px"> {/* この下もほぼ公式documentのパクリなんだよね */} <Button onClick={onOpen}>追加する</Button> <Modal isOpen={isOpen} onClose={onClose}> <ModalOverlay /> <ModalContent> <ModalHeader>Modal Title</ModalHeader> <ModalCloseButton /> <ModalBody> <Input onChange={(e) => setTodo(e.target.value)} /> </ModalBody> <ModalFooter> <Button colorScheme="blue" mr={3} onClick={onClose}> 閉じる </Button> <Button variant="ghost" onClick={() => AddTodoToServer()}> 追加する </Button> </ModalFooter> </ModalContent> </Modal> </Center> ); }; export default AddTodo; ``` - `lib/index.js`を改造 ```javascript= const BaseURL = 'https://trello-server-331705-xxe5t7bbba-an.a.run.app/api'; export const getAllLists = async () => { const res = await fetch(`${BaseURL}/todos`, { method: 'GET', }); return res.json(); }; // 追加 export const addTodoList = async (title) => { const data = { title: title, }; const res = await fetch(`${BaseURL}/todos`, { method: 'POST', body: JSON.stringify(data), }); return res.json(); }; ``` - `src/components/TodoList.jsx`を改造 ```javascript= import { useEffect, useState } from 'react'; import { Box } from '@chakra-ui/react'; import { getAllLists } from '../lib/index'; // さっき作成したものをimport import AddTodo from './AddTodo'; const TodoList = () => { const [todos, setTodos] = useState([]); const fetchLists = async () => { const res = await getAllLists(); setTodos(res); }; useEffect(() => { fetchLists(); }, []); if (todos) { return ( <Box> {/* componentをここにおく */} <AddTodo /> {todos.map((todo) => { return ( <Box m="16px" p="16px" bg="yellow.100" borderRadius="4px" key={todo.title} > {todo.title} </Box> ); })} </Box> ); } return <></>; }; export default TodoList; ``` ### 手順2: todoのステータスを編集できる機能 - `src/components/TodoList.jsx` を魔改造 ```javascript= import { useEffect, useState } from 'react'; import { Box, Select, Flex, useToast } from '@chakra-ui/react'; import { getAllLists, changeTodoValues } from '../lib/index'; import AddTodo from './AddTodo'; const TodoList = () => { const [todos, setTodos] = useState([]); const toast = useToast(); const choices = ['not started', 'doing', 'done']; const fetchLists = async () => { const res = await getAllLists(); setTodos(res); }; const changeStatus = async (e, todo) => { const res = await changeTodoValues(todo.id, todo.title, e.target.value); if (res.message === 'success!') { toast({ title: '正常に更新されました', status: 'success', duration: 3000, isClosable: true, }); return; } toast({ title: '更新されませんでした', status: 'error', duration: 3000, isClosable: true, }); }; useEffect(() => { fetchLists(); }, []); if (todos) { return ( <Box> <AddTodo /> {todos.map((todo) => { return ( <Flex m="16px" p="16px" bg="yellow.100" borderRadius="4px" key={todo.id} > <Box>{todo.title}</Box> <Box flex="1" /> <Select defaultValue={todo.status} onChange={(e) => changeStatus(e, todo)} w="256px" > {choices.map((choice) => ( <option value={choice} key={choice}> {choice} </option> ))} </Select> </Flex> ); })} </Box> ); } return <></>; }; export default TodoList; ``` - `lib/index.js`に追加する ```javascript= const BaseURL = 'https://trello-server-331705-xxe5t7bbba-an.a.run.app/api'; export const getAllLists = async () => { const res = await fetch(`${BaseURL}/todos`, { method: 'GET', }); return res.json(); }; export const addTodoList = async (title) => { const data = { title: title, }; const res = await fetch(`${BaseURL}/todos`, { method: 'POST', body: JSON.stringify(data), }); return res.json(); }; export const changeTodoValues = async (id, title, status) => { const data = { status: status, title: title, }; const res = await fetch(`${BaseURL}/todos/${id}`, { method: 'PUT', body: JSON.stringify(data), }); return res.json(); }; ``` ### 手順3: todoを削除するボタンを作る! ::: info 自分で トライしてみましょう! ::: :::spoiler≈ ヒント1 - 作成する要素は何か(何が必要か)今までの手順を踏まえて考えてみましょう! - status を更新する機能で新しく作ったのは - select タグで選択肢を選択させた - select タグの中身が更新されたときに実行する関数を作り、select タグで呼び出した ::: :::spoiler ヒント2 - `/todos/{todo_id}` エンドポイントに対し、`DELETE` リクエストを送ります - 今までのエンドポイントを呼び出していたところに追加で呼び出します - ボタンは src/components/TodoList.jsx のselect タグの下あたりに付ければ良さそうかな - ```javascript ... </Select> <Button onClick={() => なんか呼び出す関数(todo)}>削除する</Button> ... ``` ::: --- :::info 他にもできることがたくさん! - statusに応じて背景の色を変える - todoを追加したタイミングで全部更新して反映させる ::: ## 次回までに準備して欲しいもの - Mysqlの環境構築 - (不要かもしれないけど) herokuのアカウント作成 - Python3を入れておく! ###### tags: `未完project`