Help us understand the problem. What is going on with this article?

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

概要

vte.cx とReactの練習として、データの登録や登録した内容を、一覧で表示したり、編集ができる簡単なアプリを作成しました。

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

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

ファイルは、

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

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

画面遷移

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

登録画面

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

  • 入力するフォームを10項目用意する。
  • 登録ボタンを押下しデータを登録する。
  • 登録成功時には一覧画面へ遷移させ、失敗した場合はエラー内容を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="サンプル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のなかに配列の形式で入ります。

一覧画面

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

  • 登録されたデータをテーブルで表示する
  • データが登録されていない場合は登録されていない旨を表示する
  • 新規登録ボタンを表示し、押下されたら登録画面に遷移させる
  • 登録されたデータを押下すると編集画面に遷移し、データを編集できるようにする
  • 削除ボタンを用意し、押下されたら任意のデータを削除させる
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でデータが登録されているか判定し、登録されていた場合はfeedmapで回しテーブルを表示させます。登録されていない場合は、登録されていない旨を表示します。押下された時にデータの編集ができるように、
onClickで編集画面のpathpushし画面遷移させます。また、編集したいデータも一緒に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でのデータの取得や登録などはだいぶ時間がかかりましたが、気付くことが多く理解が深まったと思います。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away