サイトアイコン 上尾市のWEBプログラマーによるブログ

「ReactでTodoアプリを作ってみよう」の感想・備忘録2

「ReactでTodoアプリを作ってみよう」の感想・忘備録1の続き

TODOアプリの作成

1つのコンポーネントで実装

  1. create-react-app todo
  2. cd todo;
  3. npm install react-router-dom
  4. App.jsを修正
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
      newTodo: '',
    };
  }
  handleChange = (e) => {
    this.setState({newTodo: e.target.value});
  }
  addTodo = () => {
    if (this.state.newTodo === ''){
      return;
    }
    const todos = this.state.todos;
    todos.push(this.state.newTodo);
    this.setState({newTodo: ''});
    this.setState({todos: todos});
  }
  deleteTodo = (e) => {
    const todos = this.state.todos;
    todos.splice(e.target.dataset.index, 1);
    this.setState({todos: todos});
  }
  render() {
    return (
      <div>
        <header>
          <h1>Todoアプリ</h1>
        </header>
        <main>
          <input value={this.state.newTodo} onChange={this.handleChange}/>
          <button onClick={this.addTodo}>追加</button>
          <h2>Todoリスト</h2>
          <ul>
          {
            this.state.todos.map((todo, i) => {
              return (
                <li key={i}>{todo}<button onClick={this.deleteTodo} data-index={i}>削除</button></li>
              );
            })
          }
            </ul>
        </main>
      </div>
    );
  }
}

複数のコンポーネントで実装

  1. TodoForm.jsを作成
import React from "react";

export default class TodoForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      newTodo: '',
    };

  }
  handleChange = (e) => {
    this.setState({newTodo: e.target.value});
  }
  addTodo = () => {
    if (this.state.newTodo === ''){
      return;
    }
    this.props.addTodo(this.state.newTodo);
    this.setState({newTodo: ''});
  }
  render() {
    return (
      <React.Fragment>
        <input value={this.state.newTodo} onChange={this.handleChange}/>
        <button onClick={this.addTodo}>追加</button>
      </React.Fragment>
    )
  }
}
  1. TodoList.jsを作成
import React from "react";

export default class TodoList extends React.Component {
  deleteTodo = (e) => {
    this.props.deleteTodo(Number(e.target.dataset.index));
  }
  render() {
    return (
      <React.Fragment>
        <h2>Todoリスト</h2>
        <ul>
          {
            this.props.todos.map((todo, i) => {
              return (
                <li key={i}>{todo}<button onClick={this.deleteTodo} data-index={i}>削除</button></li>
              );
            })
          }
        </ul>
      </React.Fragment>
    )
  }
}
  1. App.jsを修正

TodoFormには追加用メソッド、TodoListには削除用メソッドと配列todos、をpropsで渡す。


import React from 'react';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
    };
  }
  addTodo = (todo) => {
    const todos = this.state.todos;
    todos.push(todo);
    this.setState({todos: todos});
  }
  deleteTodo = (index) => {
    const todos = this.state.todos;
    todos.splice(index, 1);
    this.setState({todos: todos});
  }
  render() {
    return (
      <div>
        <header>
          <h1>Todoアプリ</h1>
        </header>
        <main>
          <TodoForm addTodo={this.addTodo}/>
          <TodoList deleteTodo={this.deleteTodo} todos={this.state.todos}/>
        </main>
      </div>
    );
  }
}

ローカルストレージの利用

  1. App.jsを修正

componentDidMount() {
  const todos = JSON.parse(localStorage.getItem('todos')) || [];
  this.setState({todos: todos});
}
// addTodoメソッド、deleteTodoメソッドに追加
localStorage.setItem('todos', JSON.stringify(this.state.todos));

コンポーネントの独立

「App.jsでデータを保持し、propsでコンポーネントに渡す」から「App.jsではデータを保持せず、コンポーネントから直接ローカルストレージにアクセス」に変更。
※追加ボタンを押してもリロードしないとリストは更新されない。

  1. App.jsを修正
    ※App.jsはコンポーネントを呼び出すだけ
    <TodoForm2/>
    <TodoList2/>

import React from 'react';
import TodoForm2 from './components/TodoForm2';
import TodoList2 from './components/TodoList2';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
    };
  }
  render() {
    return (
      <div>
        <header>
          <h1>Todoアプリ</h1>
        </header>
        <main>
          <h2>コンポーネントの独立</h2>
          <TodoForm2/>
          <TodoList2/>
        </main>
      </div>
    );
  }
}
  1. TodoForm2.jsを作成

addTodoメソッドでローカルストレージの取得・保存。
const todos = JSON.parse(localStorage.getItem('todos2')) || [];
および
localStorage.setItem('todos2', JSON.stringify(todos));

import React from "react";

export default class TodoForm2 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      newTodo: '',
    };

  }
  handleChange = (e) => {
    this.setState({newTodo: e.target.value});
  }
  addTodo = () => {
    if (this.state.newTodo === ''){
      return;
    }
    const todos = JSON.parse(localStorage.getItem('todos2')) || [];
    todos.push(this.state.newTodo);
    localStorage.setItem('todos2', JSON.stringify(todos));
    this.setState({newTodo: ''});
  }
  render() {
    return (
      <React.Fragment>
        <input value={this.state.newTodo} onChange={this.handleChange}/>
        <button onClick={this.addTodo}>追加</button>
      </React.Fragment>
    )
  }
}
  1. TodoList2.jsを作成

deleteTodoメソッドでローカルストレージに保存する。
localStorage.setItem('todos2', JSON.stringify(todos));

componentDidMountメソッドでローカルストレージの取得。
componentDidMount() {
  const todos = JSON.parse(localStorage.getItem('todos2')) || [];
  this.setState({todos: todos});
}


import React from "react";

export default class TodoList2 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [],
    };
  }

  deleteTodo = (e) => {
    const todos = this.state.todos;
    todos.splice(Number(e.target.dataset.index), 1);
    this.setState({todos: todos});
    localStorage.setItem('todos2', JSON.stringify(todos));
  }
  componentDidMount() {
    const todos = JSON.parse(localStorage.getItem('todos2')) || [];
    this.setState({todos: todos});
  }
  render() {
    return (
      <React.Fragment>
        <h2>Todoリスト</h2>
        <ul>
          {
            this.state.todos.map((todo, i) => {
              return (
                <li key={i}>{todo}<button onClick={this.deleteTodo} data-index={i}>削除</button></li>
              );
            })
          }
        </ul>
      </React.Fragment>
    )
  }
}

ルーティングの利用

  1. App.jsを修正

import { Route } from "react-router-dom";
を追加し、
<Route path="/add" component={TodoForm2} exact></Route>
<Route path="/" component={TodoList2} exact></Route>
とする。

  1. index.jsを修正

import { BrowserRouter } from "react-router-dom";
を追加し、
<BrowserRouter>
  <App />
</BrowserRouter>
とする。

  1. TodoForm2.jsを修正

import { Link } from "react-router-dom";
を追加し、
<Link to="/">一覧へ</Link>
とする。

  1. TodoList2.jsを修正

import { Link } from "react-router-dom";
を追加し、
<Link to="/add">登録フォームへ</Link>
とする。

Material-UIの利用

コンポーネントがたくさんある。

  1. インストール

npm install @material-ui/core
npm install @material-ui/icons

  1. App.jsに追記

Buttonを使う場合
import Button from '@material-ui/core/Button';
を追加し、
<Button variant="contained" color="primary">Hello World</Button>
とする。

いろいろなコンポーネントを使ったサンプル


import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import ArrowBack from '@material-ui/icons/ArrowBack';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardActions from '@material-ui/core/CardActions';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import IconButton from '@material-ui/core/IconButton';
import AddIcon from '@material-ui/icons/Add';
import DeleteIcon from '@material-ui/icons/Delete';
import Divider from '@material-ui/core/Divider';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      newTodo: ''
    }
  }
  render() {
    return (
      <div>
        <AppBar position="static">
          <Toolbar>
            <ArrowBack style={{color: 'white'}}/>
            <Typography variant="title" color="inherit" style={{width: '90%', textAlign: 'center'}}>
              My Todo
            </Typography>
          </Toolbar>
        </AppBar>
        <Card>
          <CardContent>
            <TextField> value={this.state.newTodo}></TextField>
          </CardContent>
          <CardActions>
            <Button variant="contained" color="primary">追加</Button>
            <Button variant="outlined" color="primary" aria-label="Add"><AddIcon/></Button>
          </CardActions>
        </Card>
        <list>
          {
            ['111', '222', '333'].map((str) => {
            return (
              <List>
                <ListItem key={str}>
                  <ListItemText>{str}</ListItemText>
                  <IconButton variant="fab" ariia-label="delete">
                    <DeleteIcon/>
                  </IconButton>
                </ListItem>
                <Divider/>
              </List>
              )
            })
          }
        </list>
      </div>
    );
  }
}

Webサーバ上での実行

npm run build
で生成されるbuildディレクトリをサーバへアップする。

※ ルーティングがHistoryモードの場合、/addなどを表示してからブラウザをリロードすると404エラーとなってしまう。
Rewriteで全リクエストをindex.htmlに書き換える必要がある。
https://qiita.com/yakipudding/items/78e68ef31e55f559c0dc

モバイルバージョンを終了