React （React Hooks + Redux ToolKit + TypeScript ）の構成でアプリを作成しました【Covid19dashboard】

## 環境の準備

プロジェクトを作成する

```
$ mkdir <プロジェクト名>
$ npx create-react-app . --template redux-typescript --use-npm
$ npm start
```
必要なパッケージをインストールする。

```
$ npm install axios
$ npm i @material-ui/core --legacy-peer-deps
$ npm install chart.js@2.9.3
$ npm install react-chartjs-2@2.9.0 --legacy-peer-deps
$ npm install react-countup
$ npm install react-icons
$ npm install redux react-redux redux-devtools-extension redux-thunk
```



コンポーネント・ファイル構成

```
  src
  ├─ app
      ├── hooks.ts 
      └── store.ts
  ├─ features
      └── covid
           ├── Cards
                 ├── Cards.module.css
                 └── Cards.tsx
           ├── Chart
                 ├── Chart.module.css
                 └── Chart.tsx
           ├── DashBoard
                   ├── DashBoard.module.css
　　　　　　　　└── DashBoard.tsx
         ├── PieChart
         └── PieChart.tsx
             ├── SwithCountry
                   └── SwithCountry.tsx
             ├── apiDataDaily.json
             └── covidSlice.ts
  ├── App.css
  ├── App.tsx
  ├── index.css
  └── index.tsx  
 
```

```src/ app/ store.ts
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import covidReducer from '../features/covid/covidSlice';

export const store = configureStore({
  reducer: {
    covid: covidReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;
```
```features/ covid/ Cards/ Cards.module.css
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 10px;
}

.infected {
  border-left: 5px solid #33a3ff;
  border-radius: 0% !important;
  margin: 2% 2% !important;
}

.recovered {
  border-left: 5px solid #3cb371;
  border-radius: 0% !important;
  margin: 2% 2% !important;
}

.deaths {
  border-left: 5px solid #ff3370;
  border-radius: 0% !important;
  margin: 2% 2% !important;
}
```
```features/ covid/ Cards/ Cards.tsx
import React from 'react';
import styles from './Cards.module.css';
import CountUp from 'react-countup';
import { Card, CardContent, Typography, Grid } from '@material-ui/core';

import { GiHastyGrave } from 'react-icons/gi';
import { MdLocalHospital } from 'react-icons/md';
import { AiFillLike } from 'react-icons/ai';

import { useSelector } from 'react-redux';
import { selectDaily } from '../covidSlice';

const Cards: React.FC = () => {
  const daily = useSelector(selectDaily);
  return (
    <div className={styles.container}>
      <Grid container spacing={1} justify='center'>
        <Grid item xs={12} md={3} component={Card} className={styles.infected}>
          <CardContent>
            <Typography color='textSecondary' gutterBottom>
              <MdLocalHospital />
              Infected persons
            </Typography>
            <Typography variant='h5'>
              <CountUp
                start={0}
                end={daily[daily.length - 1].Confirmed}
                duration={1.5}
                separator=','
              />
            </Typography>
          </CardContent>
        </Grid>
        <Grid item xs={12} md={3} component={Card} className={styles.recovered}>
          <CardContent>
            <Typography color='textSecondary' gutterBottom>
              <AiFillLike /> Recovered persons
            </Typography>
            <Typography variant='h5'>
              <CountUp
                start={0}
                end={daily[daily.length - 1].Recovered}
                duration={1.5}
                separator=','
              />
            </Typography>
          </CardContent>
        </Grid>
        <Grid item xs={12} md={3} component={Card} className={styles.deaths}>
          <CardContent>
            <Typography color='textSecondary' gutterBottom>
              <GiHastyGrave />
              Dead persons
            </Typography>
            <Typography variant='h5'>
              <CountUp
                start={0}
                end={daily[daily.length - 1].Deaths}
                duration={1.5}
                separator=','
              />
            </Typography>
          </CardContent>
        </Grid>
      </Grid>
    </div>
  );
};

export default Cards;
```
```features/ covid/ Chart/ Chart.module.css
.container {
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}
```

```features/ covid/ Chart/ Chart.tsx
import React from 'react';
import styles from './Chart.module.css';
import { Line } from 'react-chartjs-2';

import { useSelector } from 'react-redux';
import { selectDaily } from '../covidSlice';

const Chart: React.FC = () => {
  const daily = useSelector(selectDaily);
  const dates = daily.map(({ Date }) => Date);

  const lineChart = daily[0] && (
    <Line
      data={{
        labels: dates.map((date) => new Date(date).toDateString()),

        datasets: [
          {
            data: daily.map((data) => data.Confirmed),
            label: 'Infected',
            borderColor: '#3333ff',
            showLine: false,
          },
          {
            data: daily.map((data) => data.Recovered),
            label: 'Recovered',
            borderColor: 'green',
            showLine: false,
          },
          {
            data: daily.map((data) => data.Deaths),
            label: 'Deaths',
            borderColor: '#ff3370',
            showLine: false,
          },
        ],
      }}
    />
  );

  return <div className={styles.container}>{lineChart}</div>;
};

export default Chart;
```
```features/ covid/ DashBoard/ DashBoard.module.css
.container {
  display: flex;
  align-items: center;
  flex-direction: column;
}
```
```features/ covid/ DashBoard/ DashBoard.tsx
import React, { useEffect } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import {
  AppBar,
  Toolbar,
  Typography,
  Container,
  Grid,
} from '@material-ui/core';
import styles from './DashBoard.module.css';
import { useSelector } from 'react-redux';
import { useAppDispatch } from '../../../app/hooks';
import { selectDaily, fetchAsyncGetDaily } from '../covidSlice';
import SwitchCountry from '../SwitchCountry/SwitchCountry';
import Cards from '../Cards/Cards';
import Chart from '../Chart/Chart';
import PieChart from '../PieChart/PieChart';

const useStyles = makeStyles((theme) => ({
  title: {
    flexGrow: 1,
  },
  content: {
    marginTop: 85,
  },
}));

const DashBoard: React.FC = () => {
  const classes = useStyles();
  const dispatch = useAppDispatch();
  const daily = useSelector(selectDaily);

  useEffect(() => {
    dispatch(fetchAsyncGetDaily('japan'));
  }, [dispatch]);

  return (
    <div>
      <AppBar color='primary' position='absolute'>
        <Toolbar>
          <Typography variant='h6' className={classes.title}>
            Covid 19 Live Dashboard
          </Typography>
          <div>
            <Typography variant='body1'>
              {new Date(daily[daily.length - 1].Date).toDateString()}
            </Typography>
          </div>
        </Toolbar>
      </AppBar>

      <Container className={classes.content}>
        <div className={styles.container}>
          <SwitchCountry />
        </div>
        <Grid container spacing={3}>
          <Grid item xs={12} md={12}>
            <Cards />
          </Grid>
          <Grid item xs={12} md={7}>
            <Chart />
          </Grid>
          <Grid item xs={12} md={5}>
            <PieChart />
          </Grid>
        </Grid>
      </Container>
    </div>
  );
};

export default DashBoard;
```

```features/ covid/ PieChart/ PieChart.tsx
import React from 'react';
import { Typography } from '@material-ui/core';
import { Doughnut } from 'react-chartjs-2';

import { useSelector } from 'react-redux';
import { selectDaily } from '../covidSlice';

const PieChart: React.FC = () => {
  const daily = useSelector(selectDaily);

  const motality =
    (100 * daily[daily.length - 1].Deaths) / daily[daily.length - 1].Confirmed;

  const pieChart = daily[0] && (
    <Doughnut
      data={{
        labels: ['Infected', 'Recovered', 'Deaths'],
        datasets: [
          {
            data: [
              daily[daily.length - 1].Confirmed,
              daily[daily.length - 1].Recovered,
              daily[daily.length - 1].Deaths,
            ],
            backgroundColor: [
              'rgba(0, 0, 255, 0.5)',
              '#008080',
              'rgba(255, 0, 0, 0.5)',
            ],
            hoverBackgroundColor: ['#36A2EB', '#3cb371', '#FF6384'],
            borderColor: ['transparent', 'transparent', 'transparent'],
          },
        ],
      }}
      options={{
        legend: {
          position: 'bottom',
          labels: {
            boxWidth: 15,
          },
        },
      }}
    />
  );

  return (
    <>
      <Typography align='center' color='textSecondary' gutterBottom>
        Motarity {motality.toFixed(2)} [%]
      </Typography>
      {pieChart}
    </>
  );
};

export default PieChart;
```

```features/ covid/ SwitchCountry/ SwitchCountry.tsx
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { NativeSelect, FormControl } from '@material-ui/core';

import { useAppDispatch } from '../../../app/hooks';
import { fetchAsyncGetDaily } from '../covidSlice';

const useStyles = makeStyles((theme) => ({
  formControl: {
    marginBottom: theme.spacing(3),
    minWidth: 320,
  },
}));

const SwitchCountry: React.FC = () => {
  const classes = useStyles();
  const dispatch = useAppDispatch();

  const countries = [
    'japan',
    'us',
    'germany',
    'india',
    'france',
    'italy',
    'spain',
    'russia',
    'brazil',
    'taiwan',
    'thailand',
    'new zealand',
    'sweden',
  ];

  return (
    <FormControl className={classes.formControl}>
      <NativeSelect
        onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
          dispatch(fetchAsyncGetDaily(e.target.value))
        }
      >
        {countries.map((country, i) => (
          <option key={i} value={country}>
            {country}
          </option>
        ))}
      </NativeSelect>
    </FormControl>
  );
};

export default SwitchCountry;
```

仕様する[covid19api](https://covid19api.com/)

①『View Documentation』をクリックする。

②『By Country Total All Status』 を選択する。

③『https://api.covid19api.com/total/country』を貼り付ける。

```src/features/covid/covidSlice.ts
[
  {
    "Country": "Japan",
    "CountryCode": "",
    "Province": "",
    "City": "",
    "CityCode": "",
    "Lat": "0",
    "Lon": "0",
    "Confirmed": 0,
    "Deaths": 0,
    "Recovered": 0,
    "Active": 0,
    "Date": "2020-01-22T00:00:00Z"
  }
]
```
```src/features/covid/covidSlice.ts

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import { RootState } from '../../app/store';
import dataDaily from './apiDataDaily.json';

//パスを定義する
const apiUrl = 'https://api.covid19api.com/total/country';

//データ型を定義する　typeofで取得してtypeで定義する

type DATADAILY = typeof dataDaily;

//covidSliceのstateを定義する
type covidState = {
  daily: DATADAILY;
  country: string;
};

//initialStateを定義する
const initialState: covidState = {
  daily: dataDaily,
  country: 'Japan',
};

//非同期関数を作成する
export const fetchAsyncGetDaily = createAsyncThunk(
  'covid/getDaily',
  async (country: string) => {
    const { data } = await axios.get<DATADAILY>(`${apiUrl}/${country}`);
    return { data: data, country: country };
  }
);

//createSliceでcreateの実態を作成する
const covidSlice = createSlice({
  name: 'covid',
  initialState: initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchAsyncGetDaily.fulfilled, (state, action) => {
      return {
        ...state,
        daily: action.payload.data,
        country: action.payload.country,
      };
    });
  },
});

export const selectDaily = (state: RootState) => state.covid.daily;
export const selectCountry = (state: RootState) => state.covid.country;

export default covidSlice.reducer;
```

```App.tsx
import React from 'react';

import DashBoard from './features/covid/DashBoard/DashBoard';

function App() {
  return <DashBoard />;
}

export default App;
```

## 参考サイト
[[COVID 19アプリ編] Reactで作るコロナウイルス Live ダッシュボード
](https://www.udemy.com/course/covid-19-react-live/learn/lecture/20828106?components=add_to_cart%2Cavailable_coupons%2Cbuy_button%2Cbuy_for_team%2Ccacheable_buy_button%2Ccacheable_deal_badge%2Ccacheable_discount_expiration%2Ccacheable_price_text%2Ccacheable_purchase_text%2Ccurated_for_ufb_notice_context%2Cdeal_badge%2Cdiscount_expiration%2Cgift_this_course%2Cincentives%2Cinstructor_links%2Clifetime_access_context%2Cmoney_back_guarantee%2Cprice_text%2Cpurchase_tabs_context%2Cpurchase%2Crecommendation%2Credeem_coupon%2Csidebar_container%2Cpurchase_body_container#overview)