作成するアプリについて
railsとreactを用いたtodoアプリになります。
https://todo-app-rails-react.herokuapp.com/todoes
(part1)
(part2)
(part3)
(part4)
開発環境
- 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の記述(part3でここまで完了済み)
- NewTodo.jsの記述(part4でここまで完了済み)
- EditTodo.jsの記述
それでは進めていきましょう!
アプリ作成
2. フロントサイドの実装
2.8 EditTodo.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タグでかこって編集ページに遷移できるように編集しています!
では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を当てると...
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'を忘れないように気をつけてください!
こんな感じになっていれば成功です!
続いて機能面の実装に移ります!
まずはinputからいきます。選択したtodoのcontentの表示からいきましょう!
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'
を忘れないようにしてください!
表示されましたね!どんどんいきます。
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タグにも追記あります。
このように編集が完了します。
続いてstatusに関してですね。
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でも同じことをやっているので合わせて確認してください。
//以上省略
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ボタンを押して表示が変わることを確認しましょう!
CurrentStatusとボタン表示がちゃんと更新されていますね!
TodoList.jsの方も更新されています!
実装箇所はあと2つです!
deleteボタンの実装をしていきましょう!
//以上省略
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>
</>
)
}
//以下省略
ここまでくると特に難しいこともないのではと思います。
最後にtoastifyの実装をして完了です!
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();を忘れがちです
これができればアプリは完成です!お疲れ様でした!