vte.cxとReact Hooksで簡単なアプリ作成

#概要
[vte.cx](https://blog.vte.cx/)  とReactの練習として、データの登録や登録した内容を、一覧で表示したり、編集ができる簡単なアプリを作成しました。

vte.cxにおけるサービスの作成やスキーマの登録方法については、こちらの記事を参照してください。
https://qiita.com/stakezaki/items/e526ca061d8f004db7f5

画面は**登録画面**、**一覧画面**、**編集画面**の３画面に分け、`react-router`を使い、画面遷移をさせます。

ファイルは、

- トップページで遷移先を管理させる`index.tsx`
- 登録画面を作る`RegisterProf` 
- 一覧画面を作る`ListProf`　
- 編集画面を作る`EditProf`

の４つに分けました。要件に関しては、画面ごとに説明していきます。

#画面遷移

```js:index.tsx

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`コンポーネントを表示させるようになっています。
#登録画面

この画面の要件は以下になります。

- 入力するフォームを１０項目用意する。
- 登録ボタンを押下しデータを登録する。
- 登録成功時には一覧画面へ遷移させ、失敗した場合はエラー内容をalertで表示させる

```RegisterProf.tsx

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} />チェック１
          <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="サンプル１">サンプル１</option>
             <option value="サンプル２">サンプル２</option>
             <option value="サンプル３">サンプル３</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`のなかに配列の形式で入ります。

#一覧画面
この画面の要件は以下になります。

- 登録されたデータをテーブルで表示する
- データが登録されていない場合は登録されていない旨を表示する
- 新規登録ボタンを表示し、押下されたら登録画面に遷移させる
- 登録されたデータを押下すると編集画面に遷移し、データを編集できるようにする
- 削除ボタンを用意し、押下されたら任意のデータを削除させる

```ListProf.tsx

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`を渡し、任意のデータだけを削除します。


#編集画面
この画面の要件は以下になります。

- 一覧画面で選択
- したデータの編集が行えること。
- 更新ボタンを押下したらデータを更新し、一覧画面を表示する。一覧画面には編集後のデータが表示されるようにする。
- 更新失敗時にはエラー内容をアラートで表示させる。

```EditProf.tsx

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でのデータの取得や登録などはだいぶ時間がかかりましたが、気付くことが多く理解が深まったと思います。