In this task, you will implement the Covid dashboard. The dashboard will display the total number of cases, active cases, and recoveries for a selected country. To achieve this, we will incrementally build the dashboard by adding state to the SelectCountry
and DisplayStatistics
components. We will also fetch the country data and Covid data from the APIs.
SelectCountry
componentIn this step, we will add state to the SelectCountry
component to store the selected country. Open the src/components/select-country.tsx
file and add the following code:
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { useState } from "react";
import { CountryData } from "@/data/types";
const SelectCountry = () => {
const [country, setCountry] = useState<CountryData>({
name: "United States",
code: "US",
});
const [countryData, setCountryData] = useState<CountryData[]>([
{
name: "United States",
code: "US",
},
{
name: "Canada",
code: "CA",
},
{
name: "India",
code: "ID",
},
{
name: "United Kingdom",
code: "GB",
},
{
name: "Australia",
code: "AU",
},
]);
const handleOnCountryChange = (value: string) => {
const country = countryData.find((country) => country.code === value);
setCountry(country!);
};
return (
<div className="w-full space-y-12">
<h1 className="text-6xl">Covid Statistics</h1>
<div className="flex flex-col gap-5 justify-start w-full">
<Label className="text-xl">Select a country:</Label>
<Select value={country.code} onValueChange={handleOnCountryChange}>
<SelectTrigger className="w-full text-xl p-8 bg-white">
<SelectValue placeholder="Country..." />
</SelectTrigger>
<SelectContent>
{countryData.map((country) => (
<SelectItem value={country.code} key={country.code}>
{country.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
);
};
export default SelectCountry;
Notice that we have added state to store the selected country and the list of countries. We have also added a handleOnCountryChange
function to update the selected country when the user selects a country from the dropdown.
key
attribute in the SelectItem
componentIn the SelectItem
component, we have added a key
attribute with the value of country.code
. This is important when rendering a list of items in React. The key
attribute helps React identify which items have changed, are added, or are removed. It should be a unique value for each item in the list.
If you don’t provide a unique key
attribute, React will throw a warning in the browser console. The key
attribute should be a string or a number and should be unique among siblings.
DisplayStatistics
componentIn this step, we will add state to the DisplayStatistics
component to store the Covid data for the selected country. Open the src/components/display-statistics.tsx
file and add the following code:
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useState } from "react";
import { CovidData } from "@/data/types";
const STATISTICS: (keyof CovidData)[] = ["confirmed", "active", "recovered"];
const DisplayStatistics = () => {
const [covidData, setCovidData] = useState<CovidData>({
countryName: "United States",
countryCode: "us",
countryFlag: "https://disease.sh/assets/img/flags/us.png",
confirmed: 111820082,
active: 786167,
recovered: 109814428,
});
return (
<div className="flex w-full justify-between flex-col sm:flex-row gap-5">
{STATISTICS.map((statistic) => (
<Card className="w-full" key={statistic}>
<CardHeader>
<CardTitle className="capitalize">{statistic}</CardTitle>
</CardHeader>
<CardContent className="flex justify-right items-center gap-2">
<Avatar>
<AvatarImage src={covidData.countryFlag} />
<AvatarFallback>
{covidData.countryCode.toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="text-2xl">
{(covidData[statistic] as number).toLocaleString()}
</div>
</CardContent>
</Card>
))}
</div>
);
};
export default DisplayStatistics;
In this code snippet, we have added state to store the Covid data for the selected country. We have also added a STATISTICS
array to define the statistics we want to display. We then map over this array to display the statistics in separate cards.
SelectCountry
componentNow that we have added state to the SelectCountry
component, we can fetch the list of countries from the API. Open the src/components/select-country.tsx
file and add the following code:
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { useEffect, useState } from "react";
import { CountryData } from "@/data/types";
import { fetchCountries } from "@/data/api";
const SelectCountry = () => {
const [country, setCountry] = useState<CountryData | null>(null);
const [countryData, setCountryData] = useState<CountryData[]>([]);
useEffect(() => {
fetchCountries().then((data) => setCountryData(data));
}, []);
const handleOnCountryChange = (value: string) => {
const country = countryData.find((country) => country.code === value);
setCountry(country!);
};
return (
<div className="w-full space-y-12">
<h1 className="text-6xl">Covid Statistics</h1>
<div className="flex flex-col gap-5 justify-start w-full">
<Label className="text-xl">Select a country:</Label>
<Select value={country?.code} onValueChange={handleOnCountryChange}>
<SelectTrigger className="w-full text-xl p-8 bg-white">
<SelectValue placeholder="Country..." />
</SelectTrigger>
<SelectContent>
{countryData.map((country) => (
<SelectItem value={country.code} key={country.code}>
{country.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
);
};
export default SelectCountry;
In this code snippet, we have added an useEffect
hook to fetch the list of countries when the component mounts. The fetchCountries
function is an asynchronous function that fetches the list of countries from the API. We then update the state with the fetched data. This API related function is imported from the api
file in the data
folder.
useEffect
hookThe useEffect
hook is used to perform side effects in function components. It runs after the component has rendered and can be used to fetch data, subscribe to events, or perform cleanup. The useEffect
hook takes two arguments: a function and an array of dependencies. The function is the side effect you want to perform, and the array of dependencies is used to control when the side effect runs.
In this case, we want to fetch the list of countries when the component mounts, so we pass an empty array as the second argument to the useEffect
hook. This tells React to run the effect only once when the component mounts.
It is a common pattern to use the useEffect
hook to fetch data from an API when the component mounts. This ensures that the data is fetched only once when the component is rendered for the first time.
DisplayStatistics
componentNow that we have added state to the DisplayStatistics
component, we can fetch the Covid data for the selected country. Open the src/components/display-statistics.tsx
file and add the following code:
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useEffect, useState } from "react";
import { CovidData } from "@/data/types";
import { fetchCovidData } from "@/data/api";
const STATISTICS: (keyof CovidData)[] = ["confirmed", "active", "recovered"];
const DisplayStatistics = () => {
const [covidData, setCovidData] = useState<CovidData | null>(null);
useEffect(() => {
fetchCovidData("US").then((data) => setCovidData(data));
}, []);
return (
<div className="flex w-full justify-between flex-col sm:flex-row gap-5">
{STATISTICS.map((statistic) => (
<Card className="w-full" key={statistic}>
<CardHeader>
<CardTitle className="capitalize">{statistic}</CardTitle>
</CardHeader>
<CardContent className="flex justify-right items-center gap-2">
<Avatar>
<AvatarImage src={covidData?.countryFlag} />
<AvatarFallback>
{covidData?.countryCode.toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="text-2xl">
{covidData?.[statistic]?.toLocaleString()}
</div>
</CardContent>
</Card>
))}
</div>
);
};
export default DisplayStatistics;
In this code snippet, we have added an useEffect
hook to fetch the Covid data for the selected country when the component mounts. We are fetching the Covid data for the United States (US
) as a placeholder. We will update this to fetch the Covid data for the selected country in the next step.
DisplayStatistics
componentIn this step, we will pass the selected country from the SelectCountry
component to the DisplayStatistics
component. Open the src/App.tsx
file and update the code as follows:
import SelectCountry from "./components/select-country";
import DisplayStatistics from "./components/display-statistics";
function App() {
return (
<div className="flex flex-col justify-between items-center min-h-screen container space-y-4 max-w-4xl py-10">
<SelectCountry />
<DisplayStatistics countryCode={"US"} />
</div>
);
}
export default App;
In this code snippet, we have passed the countryCode
prop to the DisplayStatistics
component with the value "US"
. This is a placeholder value that we will update in the next step.
We should now update the DisplayStatistics
component to accept the countryCode
prop and fetch the Covid data for the selected country. Open the src/components/display-statistics.tsx
file and update the code as follows:
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useEffect, useState } from "react";
import { CovidData } from "@/data/types";
import { fetchCovidData } from "@/data/api";
const STATISTICS: (keyof CovidData)[] = ["confirmed", "active", "recovered"];
type DisplayStatisticsProps = {
countryCode: string | null;
};
const DisplayStatistics = ({ countryCode }: DisplayStatisticsProps) => {
const [covidData, setCovidData] = useState<CovidData | null>(null);
useEffect(() => {
console.log("countryCode", countryCode);
if (countryCode) {
fetchCovidData(countryCode).then((data) => setCovidData(data));
}
}, [countryCode]);
if (!countryCode) {
return null;
}
return (
<div className="flex w-full justify-between flex-col sm:flex-row gap-5">
{STATISTICS.map((statistic) => (
<Card className="w-full" key={statistic}>
<CardHeader>
<CardTitle className="capitalize">{statistic}</CardTitle>
</CardHeader>
<CardContent className="flex justify-right items-center gap-2">
<Avatar>
<AvatarImage src={covidData?.countryFlag} />
<AvatarFallback>
{covidData?.countryCode.toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="text-2xl">
{covidData?.[statistic]?.toLocaleString()}
</div>
</CardContent>
</Card>
))}
</div>
);
};
export default DisplayStatistics;
In this code snippet, we have added a countryCode
prop to the DisplayStatistics
component. We have also updated the useEffect
hook to fetch the Covid data for the selected country when the countryCode
prop changes. We have also added a conditional check to return null
if the countryCode
is null
.
Notice the type definition for the DisplayStatisticsProps
interface. This is a common pattern in React to define the props that a component accepts. It helps with type checking and provides better documentation for the component.
In React, components can accept props (short for properties) that are used to pass data from a parent component to a child component. Props are read-only and should not be modified by the child component. They are used to configure the child component and can be used to pass data, functions, or other values.
When you pass props to a component, you can access them inside the component using the props
object. For example, if you pass a prop named name
to a component, you can access it inside the component as props.name
. In the case of functional components, you can destructure the props object to access individual props directly. For example, if you have a prop named name
, you can destructure it as { name }
in the function arguments. This is a common pattern in React to make the code more readable and concise.
You pass a prop to a component by adding an attribute to the component tag. For example, <DisplayStatistics countryCode={"US"} />
passes the countryCode
prop with the value "US"
to the DisplayStatistics
component. You can pass any value as a prop, including strings, numbers, objects, functions, or even other components.
SelectCountry
componentIn this step, we modify the code so that when the user selects a country from the dropdown, the country code is set in the App
component. Open the src/App.tsx
file and update the code as follows:
import SelectCountry from "./components/select-country";
import DisplayStatistics from "./components/display-statistics";
import { useState } from "react";
function App() {
const [countryCode, setCountryCode] = useState<string | null>(null);
return (
<div className="flex flex-col justify-between items-center min-h-screen container space-y-4 max-w-4xl py-10">
<SelectCountry setCountryCode={setCountryCode} />
<DisplayStatistics countryCode={countryCode} />
</div>
);
}
export default App;
Notice how we pass a function setCountryCode
to the SelectCountry
component as a prop. This function is used to update the countryCode
state in the App
component when the user selects a country from the dropdown.
Next, update src/components/select-country.tsx
to call the setCountryCode
function when the user selects a country:
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { useEffect, useState } from "react";
import { CountryData } from "@/data/types";
import { fetchCountries } from "@/data/api";
type SelectCountryProps = {
setCountryCode: (countryCode: string | null) => void;
};
const SelectCountry = ({ setCountryCode }: SelectCountryProps) => {
const [country, setCountry] = useState<CountryData | null>(null);
const [countryData, setCountryData] = useState<CountryData[]>([]);
useEffect(() => {
fetchCountries().then((data) => setCountryData(data));
}, []);
const handleOnCountryChange = (value: string) => {
const country = countryData.find((country) => country.code === value);
setCountry(country!);
setCountryCode(value);
};
return (
<div className="w-full space-y-12">
<h1 className="text-6xl">Covid Statistics</h1>
<div className="flex flex-col gap-5 justify-start w-full">
<Label className="text-xl">Select a country:</Label>
<Select value={country?.code} onValueChange={handleOnCountryChange}>
<SelectTrigger className="w-full text-xl p-8 bg-white">
<SelectValue placeholder="Country..." />
</SelectTrigger>
<SelectContent>
{countryData.map((country) => (
<SelectItem value={country.code} key={country.code}>
{country.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
);
};
export default SelectCountry;
In this code snippet, we have added a setCountryCode
prop to the SelectCountry
component. We call this function with the selected country code when the user selects a country from the dropdown. This function is passed from the App
component and is used to update the countryCode
state in the App
component.
In React, when multiple components need to share the same state, you can lift the state up to a common ancestor component. This is known as lifting state up and is a common pattern in React to manage shared state between components. By lifting the state up to a common ancestor component, you can ensure that the state is consistent across all components that need to access or update it.
In our case, we are lifting the countryCode
state up to the App
component so that both the SelectCountry
and DisplayStatistics
components can access and update the selected country code. By doing this, we ensure that the selected country code is consistent across both components and that changes to the state are reflected in both components.
A downside of lifting state up is that it can lead to more complex state management, especially as the application grows. A common issue is known as prop drilling, where you need to pass props through multiple layers of components to reach a deeply nested component. To address this issue, you can use state management libraries like Redux or context API to manage shared state more effectively. We will explore these concepts in future tutorials.
In this task, you implemented the Covid dashboard by adding state to the SelectCountry
and DisplayStatistics
components. You fetched the list of countries and the Covid data from the APIs. You passed the selected country code from the SelectCountry
component to the DisplayStatistics
component. You also learned about lifting state up in React and how to manage shared state between components.