作成するアプリについて
railsとreactを用いたtodoアプリになります。
https://todo-app-rails-react.herokuapp.com/todoes
(part1)
(part2)
(part4)
(part5)
開発環境
- ruby3.0
- rails6.1(rails6以上必須)
- react17.0.2
- vscode
アプリの作成手順
- サーバーサイドの実装
- アプリの作成
- turbolinksの無効化
- モデル&テーブルの作成
- 初期データの作成
- top#indexの作成
- todoes_controllerの作成
- ルーティングの設定
- application_controllerの設定
- application.html.erbに追記(part1でここまで完了済み)
- フロントサイドの実装
- componentsフォルダの作成
- リセットcssと共通cssの設定
- index.jsxの記述
- フロントの実装準備
- App.jsの記述(part2でここまで完了済み)
- TodoList.jsの記述
- NewTodo.jsの記述
- EditTodo.jsの記述
早速ですが進めていきましょう!
アプリ作成
2. フロントサイドの実装
2.6 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>
</>
)
}
styleをあてていきます。まずは検索、全削除ボタンです
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で制御されています。
続いてデータベースからpart1で投入した初期データを表示させます。とりあえず表示させます。
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'
となっています。
以上のようにconsole.log(resp.data)で初期データがresp.data
が格納されていることが確認できます。
console.log(resp.data)はコメントアウトしておきましょう!
//以上省略
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>
</>
)
//以下省略
seeds.rbで投入したデータが表示されました!
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;
`
それでは機能の方を実装していきましょう!
まずは検索機能からです!
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>
</>
)
}
//以下省略
続いてRemovwAllの実装に移ります。
//以上省略
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 === ""){
//以下省略
ターミナルでrails db:seed
を実行してデータを戻しておいてください。
最後に完了ボタンの実装をします。
//以上省略
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;
`}
`
以下のように表示されるかと思います!
長くなってしまったのでここまでにしておきましょう!お疲れ様でした!次回に続きます!