#概要
vte.cx とReactの練習として、データの登録や登録した内容を、一覧で表示したり、編集ができる簡単なアプリを作成しました。
vte.cxにおけるサービスの作成やスキーマの登録方法については、こちらの記事を参照してください。
https://qiita.com/stakezaki/items/e526ca061d8f004db7f5
画面は登録画面、一覧画面、編集画面の3画面に分け、react-router
を使い、画面遷移をさせます。
ファイルは、
- トップページで遷移先を管理させる
index.tsx
- 登録画面を作る
RegisterProf
- 一覧画面を作る
ListProf
- 編集画面を作る
EditProf
の4つに分けました。要件に関しては、画面ごとに説明していきます。
#画面遷移
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { Route, Switch, HashRouter} from 'react-router-dom'
import RegisterProf from './RegisterProf'
import ListProf from './ListProf'
import EditProf from './EditProf'
const App = () => {
return(
<HashRouter>
<Switch>
<Route exact path = '/' component = { ListProf } />
<Route path = '/RegisterProf' component = { RegisterProf } />
<Route path = '/EditProf' component = { EditProf } />
</Switch>
</HashRouter>
)
}
ReactDOM.render(<App />, document.getElementById('container'))
index.tsx
では画面遷移の機能を実装していきます。react-router
での画面遷移には
BrowserRouter
を使ったやり方とHashRouter
を使ったやり方がありますが、今回は
HashRouter
を使いました。
<Route />
タグのpath
に指定した値がURLになり、component
に指定したコンポーネントを表示します。
今回のアプリでは、path
が/
の時はListProf
コンポーネント、/RegisterProf
の時は
RegisterProf
コンポーネント、/EditProf
の時はEditProf
コンポーネントを表示させるようになっています。
#登録画面
この画面の要件は以下になります。
- 入力するフォームを10項目用意する。
- 登録ボタンを押下しデータを登録する。
- 登録成功時には一覧画面へ遷移させ、失敗した場合はエラー内容をalertで表示させる
import * as React from 'react'
import { useState } from 'react'
import { withRouter } from 'react-router-dom'
import axios from 'axios'
const RegisterProf = (props:any) => {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [memo, setMemo] = useState('')
const [gender, setGender] = useState('')
const [job, setJob] = useState('')
const [birthday, setBirthday] = useState('')
const [address, setAddress] = useState('')
const [height, setHeight] = useState('')
const [selectBox, setSelectBox] = useState('')
const [checkBox, setCheckBox] = useState('')
const putFeed = async () => {
try {
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
const count = await axios.put('/d/foo?_addids=1')
const req: VtecxApp.Entry[] = [
{
user:{
name: name,
email: email,
memo: memo,
gender: gender,
job: job,
birthday: birthday,
address: address,
height: height,
select: selectBox,
check: checkBox
},
link: [
{
"___href": "/foo/"+ count.data.feed.title,
"___rel": "self"
}
]
}
]
console.log(count)
const res = await axios.post('/d/',req)
{props.history.push('/')}
console.log(res)
alert('登録しました')
} catch (e) {
alert('登録に失敗しました')
}
}
return (
<form>
<b>登録画面</b>
<TextComponent title='名前' value={name} onChange={setName}/>
<TextComponent title='メールアドレス' value={email} onChange={setEmail}/>
<TextComponent title='職業' value={job} onChange={setJob}/>
<TextComponent title='住所' value={address} onChange={setAddress}/>
<TextComponent title='身長' value={height} onChange={setHeight}/>
<DateComponent value={birthday} onChange={setBirthday} />
<RadioComponent value={gender} onChange={setGender} />
<CheckComponent value={checkBox} onChange={setCheckBox} />
<SelectComponent value={selectBox} onChange={setSelectBox} />
<TextareaComponent value={memo} onChange={setMemo} />
<ButtonComponent value={putFeed} />
</form>
)
}
export const TextComponent = (_props:any) => {
return (
<form>
<input type="text" placeholder={_props.title} value={_props.value} onChange={(e:any) => _props.onChange(e.target.value)} /><br />
</form>
)
}
export const DateComponent = (_props:any) => {
return (
<form>
<label htmlFor="calendar">生年月日:</label><br />
<input type="date" max="9999-12-31" value={_props.value} onChange={(e:any) => _props.onChange(e.target.value)} /><br />
</form>
)
}
export const RadioComponent = (_props:any) => {
return (
<form>
<input type="radio" name='gender' onChange={() => _props.onChange('男')} checked={_props.value === '男' && true} /><label htmlFor="gender">男</label>
<input type="radio" name='gender' onChange={() => _props.onChange('女')} checked={_props.value === '女' && true} /><label htmlFor="gender">女</label><br />
</form>
)
}
export const CheckComponent = (_props:any) => {
return (
<form>
<input type="checkbox" name='チェックボックス' onChange={() => _props.onChange('チェック1')} checked={_props.value === 'チェック1' && true} />チェック1
<input type="checkbox" name='チェックボックス' onChange={() => _props.onChange('チェック2')} checked={_props.value === 'チェック2' && true}/>チェック2
<input type="checkbox" name='チェックボックス' onChange={() => _props.onChange('チェック3')} checked={_props.value === 'チェック3' && true}/>チェック3<br />
</form>
)
}
export const SelectComponent = (_props:any) => {
return (
<form>
<select value={_props.value} onChange={(e:any) => _props.onChange(e.target.value)}>
<option value="サンプル1">サンプル1</option>
<option value="サンプル2">サンプル2</option>
<option value="サンプル3">サンプル3</option>
</select><br />
</form>
)
}
export const TextareaComponent = (_props:any) => {
return (
<form>
<textarea value={_props.value} onChange={(e:any) => _props.onChange(e.target.value)} placeholder="メモ"></textarea><br />
</form>
)
}
const ButtonComponent = (_props:any) => {
return (
<form>
<button onClick={_props.value}>登録</button>
</form>
)
}
export default withRouter(RegisterProf)
登録ボタンが押下されるたびに新しいデータを更新するため、キーの後にaddids
を使い採番カウンタの加算を行います。
書き方は_addids={加算数}
のように書きます。今回の登録画面では、データを登録するたびに採番の値を+1して返すという風になっています。
登録されたデータはuser
のなかに配列の形式で入ります。
#一覧画面
この画面の要件は以下になります。
- 登録されたデータをテーブルで表示する
- データが登録されていない場合は登録されていない旨を表示する
- 新規登録ボタンを表示し、押下されたら登録画面に遷移させる
- 登録されたデータを押下すると編集画面に遷移し、データを編集できるようにする
- 削除ボタンを用意し、押下されたら任意のデータを削除させる
import * as React from 'react'
import { useState, useEffect,} from 'react'
import axios from 'axios'
import { withRouter } from 'react-router-dom'
const ListProf = (props:any) => {
const [feed, setfeed] = useState<VtecxApp.Entry[]>([])
const getFeed = async () => {
try {
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
const res = await axios.get('/d/foo?f')
setfeed(res.data)
} catch {
alert('データの取得に失敗しました')
}
}
useEffect(() => {
getFeed()
},[])
const deleteEntry = async (entry:VtecxApp.Entry) => {
try {
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
if(entry && entry.link) {
const key = entry.link[0].___href
const res = await axios.delete('/d'+ key)
console.log(res)
getFeed()
}
alert('削除しました')
} catch (e) {
alert('削除できませんでした')
}
}
return (
<div>
{ feed.length > 0 ?
<table>
<tr>
<th>名前</th>
<th>メール</th>
<th>職業</th>
<th>住所</th>
<th>身長</th>
<th>誕生日</th>
<th>性別</th>
<th>チェック</th>
<th>セレクト</th>
<th>メモ</th>
</tr>
{feed.map((entry) => (
<tr>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.name}, data: entry, title: 'name', type: 'text'})}>
{entry.user!.name}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.email},data: entry, title: 'email', type: 'text'})}>
{entry.user!.email}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.job},data: entry, title:'job', type: 'text'})}>
{entry.user!.job}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.address},data: entry, title: 'address', type: 'text'})}>
{entry.user!.address}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.height},data: entry, title: 'height', type: 'text'})}>
{entry.user!.height}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.birthday},data: entry, title: 'birthday'})}>
{entry.user!.birthday}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.gender},data: entry, title: 'gender'})}>
{entry.user!.gender}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.check},data: entry, title: 'check'})}>
{entry.user!.check}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.select},data: entry, title: 'select'})}>
{entry.user!.select}</a></td>
<td><a onClick={() => props.history.push({pathname: '/EditProf',
state: {text:entry.user!.memo},data: entry, title: 'memo'})}>
{entry.user!.memo}</a></td>
<td><button onClick={() => deleteEntry(entry)}>削除</button></td>
</tr>
))}
</table>
:
<p>登録されていません</p>
}
<button onClick={() => props.history.push('/RegisterProf')}>新規登録</button>
</div>
)
}
export default withRouter(ListProf)
まずは、useEffect
でレンダー後にgetFeed
を実行し登録されたデータを取得します。
取得したデータはfeed
に格納し、feed.length > 0
でデータが登録されているか判定し、登録されていた場合はfeed
をmap
で回しテーブルを表示させます。登録されていない場合は、登録されていない旨を表示します。押下された時にデータの編集ができるように、
onClick
で編集画面のpath
をpush
し画面遷移させます。また、編集したいデータも一緒にpush
することで編集画面で編集するデータを受け取ることができるようになります。
次に削除ボタンが押下されたら任意のデータを削除させる実装をします。
テーブルの中の削除ボタンが押下されたら、deleteEntry
を実行し削除処理を行います。
delteFeed
の引数にindex
を渡し、任意のデータだけを削除します。
#編集画面
この画面の要件は以下になります。
- 一覧画面で選択
- したデータの編集が行えること。
- 更新ボタンを押下したらデータを更新し、一覧画面を表示する。一覧画面には編集後のデータが表示されるようにする。
- 更新失敗時にはエラー内容をアラートで表示させる。
import * as React from 'react'
import {useState} from 'react'
import { withRouter } from 'react-router-dom'
import axios from 'axios'
import {TextComponent, DateComponent, RadioComponent, CheckComponent, SelectComponent, TextareaComponent} from './RegisterProf'
const EditProf = (props:any) => {
const input_user = props.history.location.data
const input_value = props.history.location.title
const input_key = input_user.link[0].___href
const [text, setText] = useState(props.history.location.state.text)
const putFeed = async () => {
try {
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
const req: VtecxApp.Entry[] = [
{
user:{
name: input_value === 'name' ? text: input_user.user.name,
email: input_value === 'email' ? text: input_user.user.email,
memo: input_value === 'memo' ? text: input_user.user.memo,
gender: input_value === 'gender' ? text: input_user.user.gender,
job: input_value === 'job' ? text: input_user.user.job,
birthday: input_value === 'birthday' ? text: input_user.user.birthday,
address: input_value === 'address' ? text: input_user.user.address,
height: input_value === 'height' ? text: input_user.user.height,
select: input_value === 'select' ? text: input_user.user.select,
check: input_value === 'check' ? text: input_user.user.check,
},
link: [
{
"___href": input_key,
"___rel": "self"
}
],
id: input_user.id
}
]
const res = await axios.put('/d/foo',req)
{props.history.push('/')}
console.log(res)
alert('更新しました')
} catch (e) {
alert('更新に失敗しました')
}
}
console.log('text', text)
return (
<div>
<b>編集画面</b> <br />
{ props.history.location.type === 'text' &&<TextComponent value={text} onChange={setText} />}
{ input_value === 'gender' &&<RadioComponent value={text} onChange={setText}/>}
{ input_value === 'birthday' && <DateComponent value={text} onChange={setText}/>}
{ input_value === 'check' && <CheckComponent value={text} onChange={setText} />}
{ input_value === 'select' && <SelectComponent value={text} onChange={setText} />}
{ input_value === 'memo' &&<TextareaComponent value={text} onChange={setText} />}
<button onClick={putFeed}>更新</button>
</div>
)
}
export default withRouter(EditProf)
まずは一覧画面から受け取った値を格納します。編集するデータはuseState
で管理し、フォームに表示させます。
次に更新ボタン押下時の処理を実装します。更新ボタンが押下されたらputFeed
を実行しデータの更新を行います。更新するデータは一つだけなので、三項演算子を使い判定がtrue
のところだけに編集したデータが更新されるようにします。
#終わりに
今回のアプリ作成でReactのHooksを使った書き方、react-routerでの画面遷移やaxiosの基本的なことが勉強できました。
特にaxiosでのデータの取得や登録などはだいぶ時間がかかりましたが、気付くことが多く理解が深まったと思います。