目次
- 初めに
- 完成品
- 前提条件
- 各コンポーネントについて
- Appコンポーネント
- Headerコンポーネント
- Formコンポーネント
- ToDoListコンポーネント
- 最終的なコード
- 参考
初めに
React Hooks Advent Calendar 2019 10日目の記事を書かせていただきました。
まだReactを勉強し始めて間もないですが、やはりTo Doリストは一度作っておくべきだろうと思って色々Qiitaを漁っていてあることに気が付きました。
「React Hookを使って実装している人、ほとんどいないな...」と(私が見てないだけだと思います)。
そこで私はReact Hookの一つ、useStateを使ってTo Doリストを作りました。変数名とか結構ガバガバだと思います。
React初心者ですがお叱りなどあったら甘んじて受け入れる所存です。よろしくお願いします。
※React Hooks Advent Calendar 2019 7日目の@daishiさんの記事では私とは別のやり方でTo Doリスト作ってますね。
完成品
完成品がこちらです。Netlifyでデプロイもしてます。→ To Do List Used By Hooks
私はブラウザはなんとなくVivaldi使っているのでVivaldiでアクセスした画面になります。
スタイルはほぼ全部Bootstrap 4
に投げました。
コードはGitHubに投げました。
前提条件
- Windows 10
- node.js v12.13.1
- yarn 1.19.2
- Bootstrap 4
- create-react-app 3.2.0
各コンポーネントについて
App
を親コンポーネントにして、画面上からHeader
、Form
、ToDoList
で構成しました。
Appコンポーネント
return
の中の不自然に見える<div>
タグはBootstrap
であれこれするためにこうなってます...
import React, {useState} from 'react';
import './App.css';
import Header from './Header';
import Form from './Form'
import ToDoList from './ToDoList'
const App = () => {
// ToDoリストのStateをtoDoListと定義
const [toDoList, setToDoList] = useState([]);
// toDoListに項目を追加
const addToDoList = (Title, Content) => {
setToDoList(toDoList.concat({"title": Title, "content": Content}));
}
// toDoListの項目を削除
const deleteToDoList = (index) => {
setToDoList(toDoList.filter(item => toDoList[index] !== item));
}
return (
<div>
<Header headerTitle="To Do List Used By Hooks"/>
<div>
<div>
<Form add={addToDoList}/>
<ToDoList list={toDoList} delete={deleteToDoList}/>
</div>
</div>
</div>
);
}
export default App;
Headerコンポーネント
header
タグをrenderしているだけなので,あまり書くことないです。
import React from 'react';
import './Header.css'
const Header = (props) => {
return (
<header>
<h1>{props.headerTitle}</h1>
</header>
);
}
export default Header;
Formコンポーネント
このコンポーネント、できればステートレスにしたかったんですけど上手くいきませんでした。
AppコンポーネントからaddToDoList()
をpropsとして引き渡してます。
import React, {useState} from 'react';
import './Form.css'
const Form = (props) => {
const [toDoTitle, setToDoTitle] = useState("");
const [toDoContent, setToDoContent] = useState("");
// Titleフォームの状態の制御
const handleToDoTitleInputChange = (e) => {
setToDoTitle(e.target.value);
}
// Contentフォームの状態の制御
const handleToDoContentInputChange = (e) => {
setToDoContent(e.target.value);
}
// 入力フォームのクリア
const resetInputField = () => {
setToDoTitle("");
setToDoContent("");
}
// 項目の追加を確定
const callAddToDoList = (e) => {
e.preventDefault();
props.add(toDoTitle,toDoContent);
resetInputField();
}
return (
<form>
<div>
<div>
<span>Title</span>
</div>
<input type="text"
onChange={handleToDoTitleInputChange}
value={toDoTitle}
/>
</div>
<div>
<div>
<span>Detail</span>
</div>
<textarea
onChange={handleToDoContentInputChange}
value={toDoContent}
></textarea>
</div>
<div>
<button type="submit" onClick={callAddToDoList} >
ADD
</button>
</div>
</form>
);
}
export default Form;
ToDoListコンポーネント
AppコンポーネントのStateであるtoDoList
と項目を削除する関数deleteToDoList()
をpropsとして引き渡しています。
import React from 'react';
import './ToDoList.css'
const ToDoList = (props) => {
// AppコンポーネントのStateであるtoDoListをpropsとして受け取って
// mapでループする
const toDoListItems = props.list.map(
(item, i) => {
return (
<div key={i} >
<div>
<h5>Title: {item.title}</h5>
<p>Content: {item.content}</p>
<button onClick={() => props.delete(i)} >
Delete
</button>
</div>
</div>
);
}
);
return (
<div>
<h1>Your Tasks: {props.list.length}</h1>
<div>
{toDoListItems}
</div>
</div>
);
}
export default ToDoList;
最終的なコード
上のコードにBootstrap4
でスタイルを付ける為にclassNameをつけ足していった結果が以下のコードになります。
(CSSファイルは割愛しました。)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<title>To Do List used by Hooks</title>
</head>
<body>
<div id="root"></div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
import React, {useState} from 'react';
import './App.css';
import Header from './Header';
import Form from './Form'
import ToDoList from './ToDoList'
const App = () => {
const [toDoList, setToDoList] = useState([]);
const addToDoList = (Title, Content) => {
setToDoList(toDoList.concat({"title": Title, "content": Content}));
}
const deleteToDoList = (index) => {
setToDoList(toDoList.filter(item => toDoList[index] !== item));
}
return (
<div className="toDo-app">
<Header headerTitle="To Do List Used By Hooks"/>
<div className="toDo-app-body container">
<div className="toDo-main">
<Form add={addToDoList}/>
<ToDoList list={toDoList} delete={deleteToDoList}/>
</div>
</div>
</div>
);
}
export default App;
import React from 'react';
import './Header.css'
const Header = (props) => {
return (
<header className="toDo-header">
<h1>{props.headerTitle}</h1>
</header>
);
}
export default Header;
import React, {useState} from 'react';
import './Form.css'
const Form = (props) => {
const [toDoTitle, setToDoTitle] = useState("");
const [toDoContent, setToDoContent] = useState("");
const handleToDoTitleInputChange = (e) => {
setToDoTitle(e.target.value);
}
const handleToDoContentInputChange = (e) => {
setToDoContent(e.target.value);
}
const resetInputField = () => {
setToDoTitle("");
setToDoContent("");
}
const callAddToDoList = (e) => {
e.preventDefault();
props.add(toDoTitle,toDoContent);
resetInputField();
}
return (
<form className="toDo-form">
<div className="toDo-form-title input-group">
<div className="input-group-prepend">
<span className="input-group-text">Title</span>
</div>
<input type="text"
className="form-control shadow"
onChange={handleToDoTitleInputChange}
value={toDoTitle}
/>
</div>
<div className="toDo-form-detail input-group">
<div className="input-group-prepend">
<span className="input-group-text">Detail</span>
</div>
<textarea className="form-control shadow"
aria-label="Detail"
onChange={handleToDoContentInputChange}
value={toDoContent}
></textarea>
</div>
<div className="toDo-form-add">
<button className="btn btn-success"
type="submit" onClick={callAddToDoList}
>
ADD
</button>
</div>
</form>
);
}
export default Form;
import React from 'react';
import './ToDoList.css'
const ToDoList = (props) => {
const toDoListItems = props.list.map(
(item, i) => {
return (
<div key={i} className="card toDo-item">
<div className="card-body">
<h5 className="card-title">Title: {item.title}</h5>
<p className="card-text">Content: {item.content}</p>
<button className="btn btn-danger"
onClick={() => props.delete(i)} >
Delete
</button>
</div>
</div>
);
}
);
return (
<div className="toDo-List">
<h1>Your Tasks: {props.list.length}</h1>
<div>
{toDoListItems}
</div>
</div>
);
}
export default ToDoList;
参考
To Doリストのベースはこちらの記事で学ばせてもらいました。
Formコンポーネントを作る際にかなり参考にしました。
(2020年のフロントエンドマスターになりたければこの9プロジェクトを作れで紹介されていました)。