0
1

More than 1 year has passed since last update.

railsとreactでtodoアプリを作る!(part5)

Last updated at Posted at 2021-11-03

作成するアプリについて

railsとreactを用いたtodoアプリになります。
https://todo-app-rails-react.herokuapp.com/todoes

(part1)

(part2)

(part3)

(part4)

開発環境

  • ruby3.0
  • rails6.1(rails6以上必須)
  • react17.0.2
  • vscode

アプリの作成手順

  1. サーバーサイドの実装
    1. アプリの作成
    2. turbolinksの無効化
    3. モデル&テーブルの作成
    4. 初期データの作成
    5. top#indexの作成
    6. todoes_controllerの作成
    7. ルーティングの設定
    8. application_controllerの設定
    9. application.html.erbに追記(part1でここまで完了済み)
  2. フロントサイドの実装
    1. componentsフォルダの作成
    2. リセットcssと共通cssの設定
    3. index.jsxの記述
    4. フロントの実装準備
    5. App.jsの記述(part2でここまで完了済み)
    6. TodoList.jsの記述(part3でここまで完了済み)
    7. NewTodo.jsの記述(part4でここまで完了済み)
    8. EditTodo.jsの記述

それでは進めていきましょう!

アプリ作成

2. フロントサイドの実装

2.8 EditTodo.jsの記述

さぁ、最後の章です!頑張っていきましょう!僕も頑張って書いていきます!

まずは編集ページへのリンクを作成します!

app/javascript/components/TodoList.js
import axios from 'axios'
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import styled from 'styled-components'

export const TodoList = () => {

  const [todoes, setTodoes] =

//中略

return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input 
          type="text"
          placeholder="search todo ..."
          onChange={e => {
            setSearchContent(e.target.value)
          }} />
        <RemoveAllBtn onClick={onClickRemoveAllBtn} >RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        {todoes.filter((val) => {
          if(searchContent === ""){
            return val
          }else if(val.content.toLowerCase().includes(searchContent.toLowerCase())){
            //検索ワードに対して検索されるようにフィルターにかける。
            return val
          }
          //filterにかけたtodoに対してmapで展開していく。
        }).map((val, key) => {
          //格納された全てのtodoをmapで展開していきます。
          return(
            <List key={key}>
              <TodoContent complete={val.complete}>{val.content}</TodoContent>
              <Btns>
                {val.complete ? (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >complete</Btn>
                ) : (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >incomplete</Btn>
                )}
                  <Link to={`/todoes/${val.id}/edit`}>
                    <Btn>edit</Btn>
                  </Link>

              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}
//以下省略

これはTodoList.jsです!

import { Link } from 'react-router-dom'が追記されています! 編集→editに書き換えました。(実装上の問題はありません。見た目の問題です。ごめんなさい。) またLinkタグでかこって編集ページに遷移できるように編集しています!

editボタンを押して以下のページに遷移すれば成功です!
スクリーンショット 2021-11-01 21.33.09.png

ではEditTodo.jsの編集に移ります。
まずは以下の通りに

app/javascript/components/EditTodo.js
import React from 'react'
import styled from 'styled-components'

export const EditTodo = () => {
  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <input 
        type="text"/>
      <button>update</button>
      <h2>CurrentStatus</h2>
      <div>
        Complete Incomplete
      </div>
      <h2>EditStatus</h2>
      <div>
        <button>complete</button>
        <button>incomplete</button>
        <button>delete</button>
      </div>
    </>
  )
}

ここにstyleを当てると...

app/javascript/components/EditTodo.js
import React from 'react'
import styled from 'styled-components'

export const EditTodo = () => {
  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput type="text" />
        <UpdateBtn>update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        Complete Incomplete
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}

const EditInputAndBtn = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`

const EditInput = styled.input`
  font-size: 20px;
  width: 100%;
  height: 40px;
  margin: 10px 0;
  padding: 10px;
`

const UpdateBtn = styled.button`
  width: fit-content;
  height: 40px;
  background: #ccffcc;
  border: none;
  font-weight: 500;
  margin-left: 10px;
  padding: 5px 10px;
  border-radius: 3px;
  cursor: pointer;
`

const Status = styled.p`
  font-size: 18px;
  text-align: center;
`

const Btns = styled.div`
  width: 100%;
  display: flex;
  justify-content: space-around;
`

const CompleteBtn = styled.button`
  width: fit-content;
  font-size: 20px;
  padding: 0 5px;
  margin: 0 5px;
  &:hover{
    background-color: #ccffcc;
  }
`

const IncompleteBtn = styled.button`
  width: fit-content;
  font-size: 20px;
  padding: 0 5px;
  margin: 0 5px;
  &:hover{
    background-color: #ccffcc;
  }
`

const DeleteBtn = styled.button`
  width: fit-content;
  font-size: 20px;
  padding: 0 5px;
  margin: 0 5px;
  &:hover{
    background-color: #cc3366;
  }
`

import styled from 'styled-components'を忘れないように気をつけてください!

スクリーンショット 2021-11-01 21.48.18.png

こんな感じになっていれば成功です!

続いて機能面の実装に移ります!
まずはinputからいきます。選択したtodoのcontentの表示からいきましょう!

app/javascript/components/EditTodo.js
import styled from 'styled-components'
import axios from 'axios';
import React, { useEffect, useState } from 'react'

export const EditTodo = (props) => {

  const initialTodoStatus = {
    id: null,
    content: "",
    complete: false
  };

  const [currentTodo, setCurrentTodo] = useState(initialTodoStatus)

  const getTodo = id => {
    axios.get(`/api/v1/todoes/${id}`)
    //idからレスポンスされるデータを受け取り、setCurrentTodoへ
    .then(resp => {
      setCurrentTodo(resp.data);
    })
    .catch(e => {
      console.log(e);
    })
  }

  useEffect(() => {
    getTodo(props.match.params.id)
    //URLからidを取得
    console.log(props.match.params.id)//これで確認できます
  }, [props.match.params.id]);//idの変更でuseEffectが走るようにします。(editページが開いたら実行される)

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          content="content" />
        <UpdateBtn>update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        Complete Incomplete
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略

import axios from 'axios'; import React, { useEffect, useState } from 'react' を忘れないようにしてください!

スクリーンショット 2021-11-01 22.44.21.png

表示されましたね!どんどんいきます。

app/javascript/components/EditTodo.js
import styled from 'styled-components'
import axios from 'axios';
import React, { useEffect, useState } from 'react'

export const EditTodo = (props) => {

  const initialTodoStatus = {
    id: null,
    content: "",
    complete: false
  };

  const [currentTodo, setCurrentTodo] = useState(initialTodoStatus)

  const getTodo = id => {
    axios.get(`/api/v1/todoes/${id}`)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
    .catch(e => {
      console.log(e);
    })
  }

  useEffect(() => {
    getTodo(props.match.params.id)
    //URLからidを取得
    console.log(props.match.params.id)
  }, [props.match.params.id]);

  const onChangeEditTodo = event => {
    const { name, value } = event.target;
    console.log(name)//content 
    console.log(value)//編集した文字列
    setCurrentTodo({ ...currentTodo, [name]: value });
    //todoを展開して、カラム名を指定してvalueに更新します。
  };

  const onClickUpdateTodo = () => {
    axios.patch(`/api/v1/todoes/${currentTodo.id}`, currentTodo)
    .then(resp => {
      props.history.push("/todoes");
    })
    .catch(e => {
      console.log(e);
    });
  };

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        Complete Incomplete
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略

EditInputタグ、UpdateBtnタグにも追記あります。

以上のようにできれば
スクリーンショット 2021-11-01 23.05.00.png
スクリーンショット 2021-11-01 23.05.58.png

このように編集が完了します。

続いてstatusに関してですね。

app/javascript/components/EditTodo.js
return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        {currentTodo.complete ? "Complete" : "Incomplete" }
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        <CompleteBtn>complete</CompleteBtn>
        <IncompleteBtn>incomplete</IncompleteBtn>
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}

Statusタグを編集しました。ここはcurrentTodoのcompleteカラムを確認して三項演算子で表示を変えています。

さらにボタンの実装をしていきます。ここはTodoList.jsでも同じことをやっているので合わせて確認してください。

app/javascript/components/EditTodo.js
//以上省略
const onClickUpdateTodo = () => {
    axios.patch(`/api/v1/todoes/${currentTodo.id}`, currentTodo)
    .then(resp => {
      props.history.push("/todoes");
    })
    .catch(e => {
      console.log(e);
    });
  };

  const onClickCompleteBtn = (val) => {
    var updateVal = {
      id: val.id,
      name: val.name,
      complete: !val.complete
      //completeカラムをひっくり返します(boolean型なのでtrueならfalseに、falseならtrueに)
    };
    axios.patch(`/api/v1/todoes/${val.id}`, updateVal)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
  };

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        {currentTodo.complete ? "Complete" : "Incomplete" }
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        {currentTodo.complete ? (
          <IncompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>incomplete</IncompleteBtn>
        ) : (
          <CompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>complete</CompleteBtn>
        )}
        <DeleteBtn>delete</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略

conpleteボタンを押して表示が変わることを確認しましょう!
スクリーンショット 2021-11-03 16.20.09.png
スクリーンショット 2021-11-03 16.20.38.png
CurrentStatusとボタン表示がちゃんと更新されていますね!
スクリーンショット 2021-11-03 16.21.13.png
TodoList.jsの方も更新されています!

実装箇所はあと2つです!

deleteボタンの実装をしていきましょう!

app/javascript/components/EditTodo.js
//以上省略
const onClickCompleteBtn = (val) => {
    var updateVal = {
      id: val.id,
      name: val.name,
      complete: !val.complete
      //completeカラムをひっくり返します(boolean型なのでtrueならfalseに、falseならtrueに)
    };
    axios.patch(`/api/v1/todoes/${val.id}`, updateVal)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
  };

  const onClickDeleteBtn = () => {
    const alert = window.confirm('Do you really want to delete this TodoList?')
    if (alert) {
      axios.delete(`/api/v1/todoes/${currentTodo.id}`)
      .then(resp => {
        console.log(resp.data)
        props.history.push("/todoes");
      })
      .catch(e => {
        console.log(e)
      })
    }
  }

  return (
    <>
      <h1>EditTodo</h1>
      <h2>CurrentTodo</h2>
      <EditInputAndBtn>
        <EditInput 
          type="text"
          value={currentTodo.content}
          name="content"
          onChange={onChangeEditTodo} />
        <UpdateBtn onClick={onClickUpdateTodo} >update</UpdateBtn>
      </EditInputAndBtn>
      <h2>CurrentStatus</h2>
      <Status>
        {currentTodo.complete ? "Complete" : "Incomplete" }
      </Status>
      <h2>EditStatus</h2>
      <Btns>
        {currentTodo.complete ? (
          <IncompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>incomplete</IncompleteBtn>
        ) : (
          <CompleteBtn onClick={() => onClickCompleteBtn(currentTodo)}>complete</CompleteBtn>
        )}
        <DeleteBtn onClick={onClickDeleteBtn}>削除</DeleteBtn>
      </Btns>
    </>
  )
}
//以下省略

ここまでくると特に難しいこともないのではと思います。

スクリーンショット 2021-11-03 16.29.25.png
削除ボタンをクリックすると...
スクリーンショット 2021-11-03 16.29.58.png
削除が完了しました!

最後にtoastifyの実装をして完了です!

app/javascript/components/EditTodo.js
mport styled from 'styled-components'
import axios from 'axios';
import React, { useEffect, useState } from 'react'
import { toast } from 'react-toastify'
import "!style-loader!css-loader!react-toastify/dist/ReactToastify.css"//toastifyのcss

toast.configure();//忘れがちです

export const EditTodo = (props) => {

  const notify = () => {
    toast.success("Todo successfully updated!", {
      //NewTodo.jsをコピペするとメッセージがおかしいので修正しください。
      position: "bottom-center",
      hideProgressBar: true
    });
  }

  const initialTodoStatus = {
    id: null,
    content: "",
    complete: false
  };

  const [currentTodo, setCurrentTodo] = useState(initialTodoStatus)

  const getTodo = id => {
    axios.get(`/api/v1/todoes/${id}`)
    .then(resp => {
      setCurrentTodo(resp.data);
    })
    .catch(e => {
      console.log(e);
    })
  }

  useEffect(() => {
    getTodo(props.match.params.id)
    console.log(props.match.params.id)
  }, [props.match.params.id]);
  const onChangeEditTodo = event => {
    const { name, value } = event.target;
    console.log(name) 
    console.log(value)
    setCurrentTodo({ ...currentTodo, [name]: value });
  };

  const onClickUpdateTodo = () => {
    axios.patch(`/api/v1/todoes/${currentTodo.id}`, currentTodo)
    .then(resp => {
      notify();//ここでtoastを呼び出す
      props.history.push("/todoes");
    })
    .catch(e => {
      console.log(e);
    });
  };
//以下省略

import { toast } from 'react-toastify' import "!style-loader!css-loader!react-toastify/dist/ReactToastify.css"//toastifyのcss 忘れずにimportしてください。

toast.configure();を忘れがちです

スクリーンショット 2021-11-03 16.35.19.png

スクリーンショット 2021-11-03 16.37.40.png

これができればアプリは完成です!お疲れ様でした!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1