Add state to the components

Right now our components have everything hardcoded. The dropdown has two countries, and the three cards have fixed numbers. That was fine for getting the layout right, but the dashboard needs to be dynamic. Let’s bring back useState, the same hook you used in the Counter and Dice Roller, so our components can hold and update data.

Making SelectCountry stateful

Open src/components/select-country.tsx. We will add two pieces of state: the currently selected country and the list of available countries. For now, we will still hardcode the country list. We will fetch it from the API in a later section.

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { useState } from "react";  // πŸ‘€
import type { CountryData } from "@/types/country";  // πŸ‘€

const SelectCountry = () => {
  // πŸ‘€ State for the selected country and the list of countries
  const [country, setCountry] = useState<CountryData>({
    name: "United States",
    code: "US",
  });
  const [countryData] = useState<CountryData[]>([
    { name: "United States", code: "US" },
    { name: "Canada", code: "CA" },
    { name: "India", code: "IN" },
    { name: "United Kingdom", code: "GB" },
    { name: "Australia", code: "AU" },
  ]);

  // πŸ‘€ Handler for when the user picks a country
  const handleOnCountryChange = (value: string) => {
    const selected = countryData.find((c) => c.code === value);
    setCountry(selected!);
  };

  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>
            {/* πŸ‘€ Render countries dynamically from state */}
            {countryData.map((c) => (
              <SelectItem value={c.code} key={c.code}>
                {c.name}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
    </div>
  );
};

export default SelectCountry;

Instead of hardcoded <SelectItem> elements, we now map over the countryData array to render them dynamically. When the user picks a country, handleOnCountryChange finds the matching entry and updates state.

Making DisplayStatistics stateful

Open src/components/display-statistics.tsx. We will replace the three repeated cards with a single template that maps over the statistics we want to display.

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useState } from "react";  // πŸ‘€
import type { CovidData } from "@/types/covid";  // πŸ‘€

// πŸ‘€ The three statistics we want to show as cards
const STATISTICS: (keyof CovidData)[] = ["confirmed", "active", "recovered"];

const DisplayStatistics = () => {
  // πŸ‘€ State for the covid data (hardcoded for now)
  const [covidData] = 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">
      {/* πŸ‘€ Map over STATISTICS instead of repeating cards */}
      {STATISTICS.map((statistic) => (
        <Card className="w-full" key={statistic}>
          <CardHeader>
            <CardTitle className="capitalize">{statistic}</CardTitle>
          </CardHeader>
          <CardContent className="flex justify-end 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;

The STATISTICS array defines which fields to show. We map over it to render one card per statistic, using covidData[statistic] to look up the value dynamically. This avoids repeating the same card markup three times.

Run the app. It should look the same as before, but now the data comes from state instead of being hardcoded in the JSX.

Checkpoint: Commit your progress.

git add .
git commit -m "dashboard-05: Add state to SelectCountry and DisplayStatistics"
git push