DEV Community

Adrberia
Adrberia

Posted on

COVID-19 app to check cases in Venezuela states and the world

Hello, this is my first post here and wanted to introduce myself with an app I made for my personal use using react, @reduxjs/toolkit, leaflet and apexchart!

https://radinax.github.io/covid-19-chart/

For the files I'm using here you can check the repository for more information on them.

API's used:

Redux slice:

This was made using the new @reduxjs/toolkit, we used createAsyncThunk for async, which makes handling the promise resolution easier without having to write it. We can just go to extra reducers and handle the state when the resolution is pending, fulfilled or rejected.

import { 
  createSlice,
  getDefaultMiddleware,
  configureStore,
  createAsyncThunk 
} from '@reduxjs/toolkit'
import { combineReducers } from 'redux'
import axios from 'axios'

// API
export const fetchCovidVenezuelaData = createAsyncThunk(
  'covidDataVenezuela/fetchingCovidDataVenezuela',
  async country => {
    const response = await axios.get(`https://coronavirus-cities-api.now.sh/country/${country}`)
    return response.data
  }
)

export const fetchCovidGlobalData = createAsyncThunk(
  'covidDataGlobal/fetchingCovidGlobalData',
  async () => {
    const response = await axios.get('https://corona.lmao.ninja/countries')
    return response.data
  }
)

// Initial State
const initialState = {
    data: [],
    loading: false,
    error: ""
};

// Slice
const sliceCovidVenezuela = createSlice({
  name: 'covidDataVenezuela',
  initialState,
  reducers: {},
  extraReducers: {
    [fetchCovidVenezuelaData.pending]: (state) => {
      state.loading = true
    },
    [fetchCovidVenezuelaData.fulfilled]: (state, action) => {
      state.data = action.payload
      state.loading = false
      state.error = false
    },
    [fetchCovidVenezuelaData.rejected]: (state, action) => {
      state.loading = false
      state.error = action.error
    }
  }
})

const sliceCovidGlobal = createSlice({
  name: 'covidDataGlobal',
  initialState,
  reducers: {},
  extraReducers: {
    [fetchCovidGlobalData.pending]: (state) => {
      state.loading = true
    },
    [fetchCovidGlobalData.fulfilled]: (state, action) => {
      state.data = action.payload
      state.loading = false
      state.error = false
    },
    [fetchCovidGlobalData.rejected]: (state, action) => {
      state.loading = false
      state.error = action.error
    }
  }
})

// Reducers
export const covidVenezuelaReducer = sliceCovidVenezuela.reducer
export const covidGlobalReducer = sliceCovidGlobal.reducer

const reducer = combineReducers({
  covidVenezuela: covidVenezuelaReducer,
  covidGlobal: covidGlobalReducer
})

// Configuring our store which will be used in Provider to enable Global State
export const store = configureStore({
  reducer: reducer,
  middleware: [...getDefaultMiddleware({
    serializableCheck: false,
  })]
})

Apexchart

The main component which gets imported.

const ApexChart = ({ options, series, type, height, width }) => {
  return (
    <Chart 
      options={options}
      series={series}
      type={type}
      height={height}
      width={width}
    />
  )
}

How to apply it:

// Components
import ApexChart from '../ApexChart'
// utils
import defaultConfig from '../../utils/apexDefaultConfig'

const CovidVenezuelaChart = ({ data, height, isMobile, width }) => {
  const [apexConfig, setApexConfig] = useState(defaultConfig('COVID-19 VENEZUELA', isMobile))

  useEffect(() => {
    const xaxisDataVenezuela = !isEmpty(data) && data.cities.map(o => o.state)
    const yaxisCases = !isEmpty(data) && data.cities.map(o => o.cases)
    const yaxisDeaths = !isEmpty(data) && data.cities.map(o => o.deaths)
    if (!isEmpty(data)) {
      setApexConfig({
        options: { ...apexConfig.options, xaxis: { categories: xaxisDataVenezuela } },
        series: [
          { name: 'Cases', data: yaxisCases },
          { name: 'Deaths', data: yaxisDeaths }
        ] 
      })
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data])

  return (
    <ApexChart
      type='bar'
      options={apexConfig.options}
      series={apexConfig.series}
      height={height}
      width={width}
    />
  )
}

Leaflet

For the map we will use leaflet, where it needs position to know the center of the user's view, zoom to know how deep your view of the map is going to be and of course the markers which depends on the coordinates of each state in Venezuela.

import { Map, TileLayer, Marker, Tooltip } from 'react-leaflet'

const coordinates = [
  { state: 'Caracas', lat: 10.491, lng: -66.902 },
  { state: 'Miranda', lat: 10.250, lng: -66.416 },
  { state: 'Aragua', lat: 10.235, lng: -67.591 },
  { state: 'La Guaira', lat: 10.599, lng: -66.934 },
  { state: 'Los Roques', lat: 11.857, lng: -66.757 },
  { state: 'Barinas', lat: 8.622, lng: -70.207 },
  { state: 'Zulia', lat: 10.666, lng: -71.612 },
  { state: 'Falcón', lat: 11.404, lng: -69.673 },
  { state: 'Anzoátegui', lat: 10.136, lng: -64.686 },
  { state: 'Apure', lat: 7.887, lng: -67.472 },
  { state: 'Mérida', lat: 8.589, lng: -71.156 },
  { state: 'Cojedes', lat: 9.661, lng: -68.582 },
  { state: 'Monagas', lat: 9.745, lng: -63.183 },
  { state: 'Nueva Esparta', lat: 10.957, lng: -63.869 },
  { state: 'Guárico', lat: 9.911, lng: -67.353 },
  { state: 'Bolivar', lat: 8.129, lng: -63.540 },
  { state: 'Yaracuy', lat: 10.079, lng: -69.126 },
  { state: 'Sucre', lat: 10.453, lng: -64.182 }
]

const CovidVenezuelaMap = ({ data }) => {
  const position = [8.5, -66]
  const zoom = 7
  let arr = []
  const cities = data.cities || []

  cities.forEach(service => {
    coordinates.forEach(o => {
      if(service.state === o.state) {
        arr.push({ ...service, ...o })
      }
    })
  })

  const markers = arr.map(state => (
    <Marker key={state.state} position={[state.lat, state.lng]}>
      <Tooltip opacity={1}>
        <h1>{state.state}</h1>
        <div>Cases: {state.cases}</div>
        <div>Deaths: {state.deaths || 0}</div>
      </Tooltip>
    </Marker>
  ))

  return (
    <Map center={position} zoom={zoom} style={{ width: '100%', height: '100vh'}}>
      <TileLayer
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url='https://{s}.tile.osm.org/{z}/{x}/{y}.png'
      />
      {markers}
    </Map>
  );
}

Putting everything together

const mapDispatchToProps = ({ fetchCovidVenezuelaData, fetchCovidGlobalData })
const mapStateToProps = state => ({
  covidVenezuela: {
    data: state.covidVenezuela.data,
    loading: state.covidVenezuela.loading
  },
  covidGlobal: {
    data: state.covidGlobal.data,
    loading: state.covidGlobal.loading
  }
})

const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
const isMobile = vw < 450

const selectOptions = ['Venezuela Map', 'Chart by states in Venezuela', 'Chart by Countries', 'World Map']

const filterByCountry = (arr, country) => arr.filter(o => o.country === country)[0]

const Home = ({ fetchCovidVenezuelaData, fetchCovidGlobalData, covidVenezuela, covidGlobal }) => {
  const [view, setView] = useState(selectOptions[0])
  const [venezuelaData, setVenezuelaData] = useState([])
  const [globalData, setGlobalData] = useState([])
  const [selectedCountry, setSelectedCountry] = useState('Venezuela')

  const dashboardData = view === selectOptions[2]
    ? filterByCountry(globalData, selectedCountry)
    : filterByCountry(globalData, 'Venezuela')

  const onChange = e => setView(e.target.value)
  const countryHandler = value => setSelectedCountry(value)

  useEffect(() => {
    // SETS DATA FOR GLOBAL AND VENEZUELA
    if (isEmpty(covidVenezuela.data)) fetchCovidVenezuelaData('venezuela')
    if (isEmpty(covidGlobal.data)) fetchCovidGlobalData('Venezuela')
    const responseVenezuela = covidVenezuela.data || []
    const responseGlobal = covidGlobal.data || []
    setVenezuelaData(responseVenezuela)
    setGlobalData(responseGlobal)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [covidVenezuela, fetchCovidVenezuelaData])

  const dashboard = (
    <Dashboard data={dashboardData} globalData={globalData} isMobile={isMobile}>      
      <Select
        name='Select View'
        value={view}
        values={selectOptions}
        onChange={onChange}
      />
    </Dashboard>
  )
  const covidVenezuelaChart = (
    <CovidVenezuelaChart
      isMobile={isMobile}
      data={venezuelaData}
      height={isMobile ? '1500px' : '750px'}
      width={isMobile ? '100%' : '1000px'}
    />
  )

  const covidGlobalChart = (
    <CovidGlobalChart
      countryHandler={countryHandler}
      data={globalData}
      height={isMobile ? '500px' : '600px'}
      width={isMobile ? '100%' : '1000px'}
    />
  )
  const covidVenezuelaMap =  <CovidVenezuelaMap data={venezuelaData}  />
  const covidGlobalMap =  <CovidGlobalMap data={globalData}  />

  if (covidVenezuela.loading || covidGlobal.loading) return <div>LOADING</div>

  return (
    <div className='container'>
      {dashboard}
      {view === selectOptions[0] && covidVenezuelaMap}
      {view === selectOptions[1] && covidVenezuelaChart}
      {view === selectOptions[2] && covidGlobalChart}
      {view === selectOptions[3] && covidGlobalMap}
    </div>
  )
}

And thats it! Inside our useEffect we apply our action to fetch the data and assign it to a local state which will be used to sent information to their respective components.

Conclusion

This app was made with the focus on personal use due to how heavy some were and I prefered to make one in base of what I wanted to see as a user, I hope you learned something new and if you have any doubts leave a comment below!

P.S: Looking for a remote job at the moment, so if you know any oportunity that might interest me, let me know :)

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.