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:
- For Venezuela specific cases: https://coronavirus-cities-api.now.sh/country/venezuela
- For the World: https://corona.lmao.ninja/countries
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='© <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.