LoginSignup
0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-04-12

環境の準備

プロジェクトを作成する

$ 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

①『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 ダッシュボード

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0