0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-11-03

作成するアプリについて

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

(part1)

(part2)

(part4)

(part5)

開発環境

  • 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の記述
    7. NewTodo.jsの記述
    8. EditTodo.jsの記述

早速ですが進めていきましょう!

アプリ作成

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

2.6 TodoList.jsの記述

app/javascript/components/Todolist.js
import React from 'react'

export const TodoList = () => {
  return (
    <>
      <input />
      <button>RemoveAll</button>
      <div>
        <span>content</span>
        <button>complete</button>
        <button>edit</button>
      </div>
    </>
  )
}

まずはこんなところでしょうか。スクリーンショット 2021-11-01 13.55.45.png

styleをあてていきます。まずは検索、全削除ボタンです

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

export const TodoList = () => {
  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input />
        <RemoveAllBtn>RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        <span>content</span>
        <button>complete</button>
        <button>edit</button>
      </div>
    </>
  )
}

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

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

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

import styled from 'styled-components'を忘れずに記述してください

h1タグはApp.cssで制御されています。

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

続いてデータベースからpart1で投入した初期データを表示させます。とりあえず表示させます。

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

export const TodoList = () => {

  const [todoes, setTodoes] = useState([])
  //todoesにデータベース内全てのtodoが格納される

  useEffect(() => {
    axios.get('/api/v1/todoes.json')
    //todoes_controller.rbにアクセスされます。そしてtodoesがjson形式で返されるのでreact側で受け取る。
    .then(resp => {
      console.log(resp.data)
      //コンソールで何が返ってくるか確認できます。
      setTodoes(resp.data)
      //これでtodoesに全てのtodoが格納されます。
    })
  }, [])
  //第二引数にからの配列を渡すことでTodoList.jsが描画された際に一度だけuseEffectが走ります。
//以下省略

import axios from 'axios'
import React, { useState, useEffect } from 'react'
となっています。

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

以上のようにconsole.log(resp.data)で初期データがresp.dataが格納されていることが確認できます。
console.log(resp.data)はコメントアウトしておきましょう!

app/javascript/components/Todolist.js
//以上省略
  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input />
        <RemoveAllBtn>RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        {todoes.map((val, key) => {
          //格納された全てのtodoをmapで展開していきます。
          return(
            <div key={key}>
              <span>{val.content}</span>
              <button>complete</button>
              <button>edit</button>
            </div>
          )
        })}
      </div>
    </>
  )
//以下省略

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

seeds.rbで投入したデータが表示されました!

app/javascript/components/Todolist.js
  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input />
        <RemoveAllBtn>RemoveAll</RemoveAllBtn>
      </InputAndRemoveAll>
      <div>
        {todoes.map((val, key) => {
          //格納された全てのtodoをmapで展開していきます。
          return(
            <List key={key}>
              <TodoContent>{val.content}</TodoContent>
              <Btns>
                <Btn>complete</Btn>
                <Btn>edit</Btn>
              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}

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

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

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

const List = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 7px auto;
  padding: 10px;
  font-size: 25px;
`

const Btns = styled.div`
  justify-content: space-around;
`

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

const TodoContent = styled.span`
  font-size: 24px;
`

cssを当てました!
スクリーンショット 2021-11-01 16.20.12.png

それでは機能の方を実装していきましょう!

まずは検索機能からです!

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

export const TodoList = () => {

  const [todoes, setTodoes] = useState([])

  useEffect(() => {
    axios.get('/api/v1/todoes.json')
    .then(resp => {
      console.log(resp.data)
      //コンソールで何が返ってくるか確認できます。
      setTodoes(resp.data)
      //これでtodoesに全てのtodoが格納されます。
    })
  }, [])

  const [searchContent, setSearchContent] = useState('')
  //searchに関するstate

  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input 
          type="text"
          placeholder="search todo ..."
          onChange={e => {
            setSearchContent(e.target.value)
          }} />
        <RemoveAllBtn>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) => {
          return(
            <List key={key}>
              <TodoContent>{val.content}</TodoContent>
              <Btns>
                <Btn>complete</Btn>
                <Btn>edit</Btn>
              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}

//以下省略

これで検索ができます。スクリーンショット 2021-11-01 16.45.47.png

続いてRemovwAllの実装に移ります。

app/javascript/components/Todolist.js
//以上省略
export const TodoList = () => {

  const [todoes, setTodoes] = useState([])

  useEffect(() => {
    axios.get('/api/v1/todoes.json')
    .then(resp => {
      console.log(resp.data)
      setTodoes(resp.data)
    })
  }, [])

  const [searchContent, setSearchContent] = useState('')

  const onClickRemoveAllBtn = () => {
    const confirm = window.confirm('Do you really want to delete all TodoLists?')
    //いきなり削除できないように確認ダイアログを表示させます。
    if(confirm){
      axios.delete('/api/v1/todoes/destroy_all')
      //todoes_controllerのdestroy_allを選択する
      .then(resp => {
        setTodoes([])
        //setTodoesをからの配列に更新することで表示を無くします。
      })
    }
  }

  return (
    <>
      <h1>Todoes</h1>
      <InputAndRemoveAll>
        <Input 
          type="text"
          placeholder="search todo ..."
          onChange={e => {
            setSearchContent(e.target.value)
          }} />
        <RemoveAllBtn onClick={onClickRemoveAllBtn} >RemoveAll</RemoveAllBtn>
         //onClickRemoveAllBtnを呼び出す
      </InputAndRemoveAll>
      <div>
        {todoes.filter((val) => {
          if(searchContent === ""){
//以下省略

ボタンをクリックすると綺麗さっぱり削除されます!
スクリーンショット 2021-11-01 17.02.46.png

ターミナルでrails db:seedを実行してデータを戻しておいてください。

最後に完了ボタンの実装をします。

app/javascript/components/Todolist.js
//以上省略

  const onClickRemoveAllBtn = () => {
    const confirm = window.confirm('Do you really want to delete all TodoLists?')
    if(confirm){
      axios.delete('/api/v1/todoes/destroy_all')
      .then(resp => {
        setTodoes([])
      })
    }
  }

  const onClickCompleteBtn = (index, val) => {
    //valは選択されたtodoの情報が格納されています
    const val2 = {
      id: val.id,
      content: val.content,
      complete: !val.complete
      //completeカラムをひっくり返します(boolean型なのでtrueならfalseに、falseならtrueに)
    }
    axios.patch(`/api/v1/todoes/${val.id}`, val2)
    .then(resp => {
      const BrandNewTodoes = [...todoes]
      BrandNewTodoes[index].complete = resp.data.complete
      //completeボタンを押されたtodoを選んで、レスポンスで返ってきたcompleteカラムを代入します
      setTodoes(BrandNewTodoes)
      //これで表示がかわります
    })
  }

  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
          }
        }).map((val, key) => {
          return(
            <List key={key}>
              <TodoContent complete={val.complete}>{val.content}</TodoContent>
              <Btns>
                {val.complete ? (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >incomplete</Btn>
                ) : (
                  <Btn onClick={() => onClickCompleteBtn(key, val)} >complete</Btn>
                )}
                <Btn>edit</Btn>
              </Btns>
            </List>
          )
        })}
      </div>
    </>
  )
}

//中略

const TodoContent = styled.span`
  font-size: 24px;
  ${({ complete }) => complete && `
    opacity: 0.4;
  `}
`

以下のように表示されるかと思います!

スクリーンショット 2021-11-01 17.33.44.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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?