フリーランス 技術調査ブログ

フリーランス/エンジニア Ruby Python Nodejs Vuejs React Dockerなどの調査技術調査の備忘録

ReactHooksでBMI計算を1日ごとの結果を一覧で表示する(redux/useContext使用)

はじめに

  • 前回、BMIの計算フォームを作成しましたが、日付事に結果を残すものにバージョンアップする px-wing.hatenablog.com
  • 同じ日付を入力したら、以前のデータを上書きする
  • reduxも利用してデータの持ち回りをする。日付事に一覧を表示させる
  • 登録したデータを削除できるようにする
  • 日付の新しい順からテーブルに一覧で表示させる 上記の実装をしてみた。

コード

  • components/bmi/BmiForm.js
import React,{useState, useContext} from 'react'
import {Form, Button} from 'react-bootstrap'

import moment from 'moment'

import AppContext from '../../contexts/AppContext'
import { 
  CREATE_BMI, 
  DELETE_BMI,
} from '../../actions'

import Bmis from './Bmis'
import '../siginin.css'
import { Formik } from 'formik'
import * as yup from 'yup'

const BmiForm = () => {
  const {state, dispatch } = useContext(AppContext)
  const [bmi, setBmi] = useState({resultBmi: 0,bestWeight: 0,bmiLevel: ''})

  const schema = yup.object({
    bodyHeight: yup.number().min(1,'身長は0以上の数値を入力して下さい。').required('身長を入力してください。'),
    bodyWeight: yup.number().min(1,'体重は0以上の数値を入力して下さい。').required('体重を入力してください。'),
    measurementDate: yup.date().typeError('正しい日付を入力してください').required('計測日を入力してください。').test('measurementDateCheck', '未来の日付を指定することはできません。', (value) => value <= new Date()),
  });

  console.log(state)
  //https://jaredpalmer.com/formik/docs/overview
  return (
      <Formik
        validationSchema={schema}
        onSubmit={(values) => {
          const resultBmi = (values.bodyWeight/((values.bodyHeight/100)*(values.bodyHeight/100))).toFixed(2)
          const bestWeight= ((values.bodyHeight/100)*(values.bodyHeight/100)*22).toFixed(2)
          let bmiLevel = '肥満'
          if(resultBmi<18.5){
            bmiLevel = 'やせ型'
          }else if(resultBmi<24.9){
            bmiLevel = '普通'
          }else if(resultBmi<30){
            bmiLevel = '肥満度1'
          }else if(resultBmi<35){
            bmiLevel = '肥満度2'
          }else if(resultBmi<40){
            bmiLevel = '肥満度3'
          }else if(resultBmi<=40){
            bmiLevel = '肥満度4'
          }

          setBmi({resultBmi,bmiLevel,bestWeight});

          dispatch({
            type: CREATE_BMI,
            measurementDate: values.measurementDate,
            bodyHeight: values.bodyHeight,
            bodyWeight: values.bodyWeight,
            resultBmi,
            bmiLevel,
            bestWeight
          })
      
        }}
        initialValues={{ bodyHeight: '', bodyWeight:'', measurementDate: moment().format('YYYY-MM-DD')}}
      >
        {
          ({handleSubmit, handleChange, handleBlur, values, errors, touched}) => (
    <>      
      <h4>BMI計算</h4>
      <Form  noValidate onSubmit={handleSubmit}>
      <Form.Group controlId="formGroupMeasurementDate">
          <Form.Label>日付</Form.Label>
          <Form.Control name="measurementDate" type="date" placeholder="計測日を入力してください" value={values.measurementDate} onBlur={handleBlur} onChange={handleChange}  isInvalid={!!(touched.measurementDate && errors.measurementDate)}/>
          <Form.Control.Feedback type="invalid">{errors.measurementDate}</Form.Control.Feedback>
        </Form.Group>
        <Form.Group controlId="formGroupName">
          <Form.Label>身長</Form.Label>
          <Form.Control name="bodyHeight" type="number" min="0" step="0.01" placeholder="身長を入力して下さい" value={values.bodyHeight} onBlur={handleBlur} onChange={handleChange}  isInvalid={!!(touched.bodyHeight && errors.bodyHeight)}/>
          <Form.Control.Feedback type="invalid">{errors.bodyHeight}</Form.Control.Feedback>
        </Form.Group>
        <Form.Group controlId="formGroupWeight">
          <Form.Label>体重</Form.Label>
          <Form.Control name="bodyWeight" type="number" min="0" step="0.01" placeholder="体重を入力して下さい" value={values.bodyWeight} onBlur={handleBlur} onChange={handleChange}  isInvalid={!!(touched.bodyWeight && errors.bodyWeight)}/>
          <Form.Control.Feedback type="invalid">{errors.bodyWeight}</Form.Control.Feedback>
        </Form.Group>
        <div className='text-center'>
          <Button variant="primary" type="submit">計算</Button>
        </div>
      </Form>
      <div>
        <h4>BMI結果</h4>
        BMI: {bmi.resultBmi}<br />
        診断: {bmi.bmiLevel}
        適正体重: {bmi.bestWeight}
      </div>
      <div className={state.bmis.length > 0 ? 'active' : 'hidden'}>
        <Bmis />
      </div>  
    </>    
      )}
      </Formik>      
  )
}

export default BmiForm
  • components/bmi/bmis.jsファイル
import React,{useContext} from 'react'
import Bmi from './Bmi' 
import AppContext from '../../contexts/AppContext'

const Bmis = () => {
  const { state } = useContext(AppContext)
  return (
    <>
      <h4>BMI履歴</h4>
      <table className="table table-hover">
        <thead>
          <tr>
            <td>日付</td>
            <td>体重</td>
            <td>BMI</td>
            <td>肥満度</td>
            <td>ベスト体重</td>
            <td>削除</td>
          </tr>  
        </thead>
      <tbody>
        { state.bmis.map((bmi, index) => (<Bmi key={index} bmi={bmi} />)) }
      </tbody>
      </table>
    </>  
  )  
}

export default Bmis
import React,{useContext} from 'react'

import { DELETE_BMI } from '../../actions'
import AppContext from '../../contexts/AppContext'

const Bmi = ({bmi}) => {
  const {dispatch} = useContext(AppContext)
  const measurementDate = bmi.measurementDate
  const handleClickDeleteButton = () => {
    const result = window.confirm(`${measurementDate}のBMIデータを削除しても良いですか?`)
    if (result) {
      dispatch({ type: DELETE_BMI,measurementDate })
    } 
  }

  return (
    <tr>
      <td>{bmi.measurementDate}</td>
      <td>{bmi.bodyWeight}</td>
      <td>{bmi.resultBmi}</td>
      <td>{bmi.bmiLevel}</td>
      <td>{bmi.bestWeight}</td>
      <td>
        <button type="button" className="btn btn-danger" onClick={handleClickDeleteButton}>削除</button>
      </td>
    </tr>
  )  
}

export default Bmi
  • contexts/AppContext.js
import { createContext } from 'react'

const AppContext = createContext()

export default AppContext
  • actions/index.js
export const CREATE_BMI = 'CREATE_BMI'
export const DELETE_BMI = 'DELETE_BMI'
  • reducers/bmis.js
import {
  CREATE_BMI,
  DELETE_BMI,
} from '../actions'

const bmis = (state = [], action) => {
  switch (action.type) {
    case CREATE_BMI:
      const bmi = { 
        bodyHeight: action.bodyHeight,
        bodyWeight: action.bodyWeight, 
        measurementDate: action.measurementDate, 
        resultBmi: action.resultBmi, 
        bmiLevel: action.bmiLevel,
        bestWeight: action.bestWeight
      }
      const notUpdateBmis = state.filter(bmi => bmi.measurementDate !== action.measurementDate)
      const UpdateBmis = [...notUpdateBmis, bmi]
      UpdateBmis.sort((a,b) =>a.measurementDate < b.measurementDate ? 1 : -1)
      return UpdateBmis
    case DELETE_BMI:
      return state.filter(bmi => bmi.measurementDate !== action.measurementDate)
    default:
      return state  
  }
}

export default bmis
  • reducers/index.js
import {combineReducers} from 'redux'
import bmis from './bmis'

export default combineReducers({bmis})
  • components/App.js (下記の内容だけでは他の情報もあるため、超ざっくりです。)
 import React, { useState, useEffect, useReducer } from 'react'
 import { Nav, Container, Form, Button, Table, Navbar } from 'react-bootstrap'
 import axios from 'axios'

 import AppContext from '../contexts/AppContext'
 import reducer from '../reducers'

  const [state, dispatch] = useReducer(reducer, initialBmiState)
  return (
    <AppContext.Provider value={{ state, dispatch }}>
    <Router>
(省略)
    </Router >
    </AppContext.Provider>

作成した画面

f:id:PX-WING:20200601232204p:plain