Help us understand the problem. What is going on with this article?

【React】ToDoアプリを作ってみよう【後編】

More than 1 year has passed since last update.

:star: 関連

:star: 目標

  • Reactで画像のようなToDoアプリを作ります。
  • 今回はTodoを投稿する機能をつけましょう。
  • 見本:GitHub

todo-app4.gif

:star: 手順

:pencil: フォームの作成

:pencil2: App.js

  • import Form from './Form'Form.jsを読み込む記述をします。
  • render(){}return()の中に<Form />を記述し、Formを呼び出します。。この時点ではForm.jsに何も記述していないのでエラーが出ます。

:pencil2: Form.js

src/Form.js
import React, { Component } from 'react'
import './css/form.css'

class Form extends Component {
  render() {
    return (
      <div className="form">
        <form>
          <input name="title" type="text" placeholder="タイトル ※必須" defaultValue="reactの勉強" /><br/>
          <textarea name="desc" placeholder="説明を入力" defaultValue="todoアプリを作っています!"></textarea><br/>
          <button type="submit">todoを作成</button>
        </form>
      </div>
    )
  }
}

export default Form
  • formタグを使って通常のHTML同様にフォームを作りましょう
  • 入力するのが面倒な場合はdefaultValueを設定しておくと楽です

:pencil: 投稿機能の実装

:pencil2: App.js

src/App.js
import React, { Component } from 'react'
import TodoList from './TodoList'
import Form from './Form'
import './css/App.css'


class App extends Component {

  constructor() {
    super()
    const todos = [
      {
        id: 1,
        title: "Hello, React!",
        desc: "React始めました",
        done: false
      },
      {
        id: 2,
        title: "Hello, Redux!",
        desc: "Reduxも始めました",
        done: false
      },
    ]
    this.state = {
      todos: todos,
      countTodo: todos.length + 1,
    }
  }

  handleSubmit(e) {
    e.preventDefault();
    const title = e.target.title.value;
    const desc = e.target.desc.value;
    const todos = this.state.todos.slice()
    const countTodo = this.state.countTodo

    todos.push({
      id: countTodo,
      title: title,
      desc: desc,
      done: false,
    });

    this.setState({ todos })
    this.setState({ countTodo: countTodo + 1 })


    e.target.title.value = '';
    e.target.desc.value = '';
  }


  render() {
    return (
      <div className="app">
        <h1>todoアプリを作ってみた</h1>
        <Form handleSubmit={this.handleSubmit.bind(this)} />
        <TodoList
          todos={this.state.todos}
          />
      </div>
    );
  }
}

export default App
  • まず、constructorを修正します。初期のtodosの中身はconst todosとし、this.stateに入れるようにしました。ややこしいですが、this.statestate名: stateの内容のように定めます。
  • countTodoというTodoの数をカウントするstateを作ります。これをidの代わりに使用します。
  • これらconstructorで定められたstateの内容は最初の一度しか呼ばれないため、Todoを投稿して増えていってもcountTodo: todos.lengthは増えていきません。Todoを作成する時にcountTodoを増やす記述が必要です。
  • <Form handleSubmit={this.handleSubmit.bind(this)} />とし、FormコンポーネントでhandleSubmit()という関数が使えるようにします。
  • this.handleSubmit(this)とすると、このthisは呼び出された先(この場合はFormコンポーネント)になってしますため、.bind(this)をつけることで、thisをこのコンポーネントに束縛できます。
  • 参考:JavaScriptの「this」は「4種類」??
  • 関数handleSubmit(e)を定義します。e.preventDefault()で画面の更新がされないようにできます。また、e.target.(name属性).valueでフォームの中身を取り出すことができます。
  • .slice()でコピーされた新しい配列を作り、stateが直接変更されることを防ぎます。
  • 参考:【Javascript】値渡しと参照渡しについてあらためてまとめてみる
  • 配列todosに新しいTodoの中身をpushし、setState({})でstateを更新します。本来はsetState({todos: todos})の様に変更前と変更後のオブジェクトを指定する必要がありますが、同じ名前の場合は今回のように省略できます。

:pencil2: Form.js

  • <form onSubmit={this.props.handleSubmit}>を記述し、propsとして受け取ったhandleSubmitをonSubmit時に発火するようにします。この時、App.jsで定義したhandleSubmit()の処理が動き、Todoの投稿ができるようになりました。

todo-app78.gif

:pencil: Todoの完了/未完了の切り替え実装

:pencil2: App.js

src/App.js
...

  setTodoStatus(clickTodo) {
    const todos = this.state.todos.slice();
    const todo = todos[clickTodo.id - 1];
    todo.done = !todo.done;
    todos[clickTodo.id - 1] = todo;

    this.setState({ todos });
  }

...

        <TodoList
          todos={this.state.todos}
          setTodoStatus={this.setTodoStatus.bind(this)}
          />

  • Todoの完了/未完了を切り替える関数setTodoStatus()を定義します。handleSubmit()の時と同じように.slice()で新しくコピーした配列を使います。
  • 配列は0から、idは1から始まるので、idから1を引いた数字で配列のTodoを取り出します。
  • todo.doneはtrue/falseのBoolean型なので、!マークで真偽を反転させることができます。これをtodosに入れ直し、setState()で更新します。
  • 関数setTodoStatusを、まずはTodoListに渡しましょう。.bind()をお忘れなく。

:pencil2: TodoList.js

src/TodoList.js
...

  render() {
    const todos = this.props.todos.map( todo =>
      <Todo
        key={todo.id}
        {...todo}
        setTodoStatus={this.props.setTodoStatus}
      />
    )
...
  • 関数setTodoStatus()をさらにTodoに渡します。

:pencil2: Todo.js

src/Todo.js
...

  render() {
    const className = this.props.done ? 'done' : 'undone';
    const link = this.props.done ? '元に戻す' : '完了!'
    return(
      <li className={className}>
        <span>{this.props.id}</span>
        <span>{this.props.title}  </span>
        <a href="" onClick={(e) => { e.preventDefault(); this.props.setTodoStatus(this.props)}}>{link}</a>  
        <p>{this.props.desc}</p>
      </li>
    );
  }

...

todo-app8.gif

:star2: 以上で基本編は終了です!

  • 途中の難しい部分について理解を深めるには参考リンクをご参照ください。
  • 内容に不備等ありましたら、お手数ですがコメントにてお願いします。

:star: 参考

mikan3rd
なんでも自分で作ってみたい盛りのエンジニア3年生。 ReactとPythonがちょっと書ける。
https://lapras.com/public/PSOCHNH
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした