DEV Community

Cover image for Displaying Country Flags in React: Emojis vs SVG
Radzion Chachura
Radzion Chachura

Posted on • Originally published at radzion.com

2

Displaying Country Flags in React: Emojis vs SVG

Watch on YouTube | 🐙 GitHub | 🎮 Demo

Let me share two methods for displaying a country flag with React.

Country code

Before delving into these options, we need an object with a two-letter country code and its name.

export const countryNameRecord = {
  AF: "Afghanistan",
  AL: "Albania",
  DZ: "Algeria",
  AS: "American Samoa",
  AD: "Andorra",
  AO: "Angola",
  AI: "Anguilla",
  AQ: "Antarctica",
  AG: "Antigua and Barbuda",
  AR: "Argentina",
  AM: "Armenia",
  AW: "Aruba",
  AU: "Australia",
  AT: "Austria",
  AZ: "Azerbaijan",
  BS: "Bahamas",
  BH: "Bahrain",
  BD: "Bangladesh",
  BB: "Barbados",
  BY: "Belarus",
  BE: "Belgium",
  BZ: "Belize",
  BJ: "Benin",
  BM: "Bermuda",
  BT: "Bhutan",
  BO: "Bolivia",
  BA: "Bosnia and Herzegovina",
  BW: "Botswana",
  BV: "Bouvet Island",
  BR: "Brazil",
  IO: "British Indian Ocean Territory",
  BN: "Brunei",
  BG: "Bulgaria",
  BF: "Burkina Faso",
  BI: "Burundi",
  KH: "Cambodia",
  CM: "Cameroon",
  CA: "Canada",
  CV: "Cape Verde",
  KY: "Cayman Islands",
  CF: "Central African Republic",
  TD: "Chad",
  CL: "Chile",
  CN: "China",
  CX: "Christmas Island",
  CC: "Cocos (Keeling) Islands",
  CO: "Colombia",
  KM: "Comoros",
  CG: "Congo",
  CK: "Cook Islands",
  CR: "Costa Rica",
  HR: "Croatia",
  CU: "Cuba",
  CY: "Cyprus",
  CZ: "Czech Republic",
  DK: "Denmark",
  DJ: "Djibouti",
  DM: "Dominica",
  DO: "Dominican Republic",
  TP: "East Timor",
  EC: "Ecuador",
  EG: "Egypt",
  SV: "El Salvador",
  GQ: "Equatorial Guinea",
  ER: "Eritrea",
  EE: "Estonia",
  ET: "Ethiopia",
  FK: "Falkland Islands",
  FO: "Faroe Islands",
  FJ: "Fiji Islands",
  FI: "Finland",
  FR: "France",
  GF: "French Guiana",
  PF: "French Polynesia",
  TF: "French Southern territories",
  GA: "Gabon",
  GM: "Gambia",
  GE: "Georgia",
  DE: "Germany",
  GH: "Ghana",
  GI: "Gibraltar",
  GR: "Greece",
  GL: "Greenland",
  GD: "Grenada",
  GP: "Guadeloupe",
  GU: "Guam",
  GT: "Guatemala",
  GG: "Guernsey",
  GN: "Guinea",
  GW: "Guinea-Bissau",
  GY: "Guyana",
  HT: "Haiti",
  HM: "Heard Island and McDonald Islands",
  VA: "Holy See (Vatican City State)",
  HN: "Honduras",
  HK: "Hong Kong",
  HU: "Hungary",
  IS: "Iceland",
  IN: "India",
  ID: "Indonesia",
  IR: "Iran",
  IQ: "Iraq",
  IE: "Ireland",
  IM: "Isle of Man",
  IL: "Israel",
  IT: "Italy",
  CI: "Ivory Coast",
  JM: "Jamaica",
  JP: "Japan",
  JE: "Jersey",
  JO: "Jordan",
  KZ: "Kazakhstan",
  KE: "Kenya",
  KI: "Kiribati",
  KW: "Kuwait",
  KG: "Kyrgyzstan",
  LA: "Laos",
  LV: "Latvia",
  LB: "Lebanon",
  LS: "Lesotho",
  LR: "Liberia",
  LY: "Libyan Arab Jamahiriya",
  LI: "Liechtenstein",
  LT: "Lithuania",
  LU: "Luxembourg",
  MO: "Macao",
  MK: "North Macedonia",
  MG: "Madagascar",
  MW: "Malawi",
  MY: "Malaysia",
  MV: "Maldives",
  ML: "Mali",
  MT: "Malta",
  MH: "Marshall Islands",
  MQ: "Martinique",
  MR: "Mauritania",
  MU: "Mauritius",
  YT: "Mayotte",
  MX: "Mexico",
  FM: "Micronesia, Federated States of",
  MD: "Moldova",
  MC: "Monaco",
  MN: "Mongolia",
  ME: "Montenegro",
  MS: "Montserrat",
  MA: "Morocco",
  MZ: "Mozambique",
  MM: "Myanmar",
  NA: "Namibia",
  NR: "Nauru",
  NP: "Nepal",
  NL: "Netherlands",
  NC: "New Caledonia",
  NZ: "New Zealand",
  NI: "Nicaragua",
  NE: "Niger",
  NG: "Nigeria",
  NU: "Niue",
  NF: "Norfolk Island",
  KP: "North Korea",
  GB: "United Kingdom of Great Britain and Northern Ireland",
  MP: "Northern Mariana Islands",
  NO: "Norway",
  OM: "Oman",
  PK: "Pakistan",
  PW: "Palau",
  PS: "Palestine",
  PA: "Panama",
  PG: "Papua New Guinea",
  PY: "Paraguay",
  PE: "Peru",
  PH: "Philippines",
  PN: "Pitcairn",
  PL: "Poland",
  PT: "Portugal",
  PR: "Puerto Rico",
  QA: "Qatar",
  RE: "Reunion",
  RO: "Romania",
  RU: "Russian Federation",
  RW: "Rwanda",
  SH: "Saint Helena",
  KN: "Saint Kitts and Nevis",
  LC: "Saint Lucia",
  PM: "Saint Pierre and Miquelon",
  VC: "Saint Vincent and the Grenadines",
  WS: "Samoa",
  SM: "San Marino",
  ST: "Sao Tome and Principe",
  SA: "Saudi Arabia",
  SN: "Senegal",
  RS: "Serbia",
  SC: "Seychelles",
  SL: "Sierra Leone",
  SG: "Singapore",
  SK: "Slovakia",
  SI: "Slovenia",
  SB: "Solomon Islands",
  SO: "Somalia",
  ZA: "South Africa",
  GS: "South Georgia and the South Sandwich Islands",
  KR: "South Korea",
  SS: "South Sudan",
  ES: "Spain",
  LK: "Sri Lanka",
  SD: "Sudan",
  SR: "Suriname",
  SJ: "Svalbard and Jan Mayen",
  SZ: "Swaziland",
  SE: "Sweden",
  CH: "Switzerland",
  SY: "Syria",
  TJ: "Tajikistan",
  TZ: "Tanzania",
  TH: "Thailand",
  CD: "The Democratic Republic of Congo",
  TL: "Timor-Leste",
  TG: "Togo",
  TK: "Tokelau",
  TO: "Tonga",
  TT: "Trinidad and Tobago",
  TN: "Tunisia",
  TR: "Turkey",
  TM: "Turkmenistan",
  TC: "Turks and Caicos Islands",
  TV: "Tuvalu",
  UG: "Uganda",
  UA: "Ukraine",
  AE: "United Arab Emirates",
  US: "United States",
  UM: "United States Minor Outlying Islands",
  UY: "Uruguay",
  UZ: "Uzbekistan",
  VU: "Vanuatu",
  VE: "Venezuela",
  VN: "Vietnam",
  VG: "Virgin Islands, British",
  VI: "Virgin Islands, U.S.",
  WF: "Wallis and Futuna",
  EH: "Western Sahara",
  YE: "Yemen",
  ZM: "Zambia",
  ZW: "Zimbabwe",
} as const

export type CountryCode = keyof typeof countryNameRecord
Enter fullscreen mode Exit fullscreen mode

Subsequently, we can create a CountryCode type that will serve as a union of all country codes.

Use Emoji to Display a Flag

As there is an emoji for every flag, utilizing them would be the easiest choice for displaying the country flag.

import {
  CountryCode,
  countryNameRecord,
} from "@reactkit/utils/countryNameRecord"
import { getCountryFlagEmoji } from "@reactkit/utils/getCountryFlagEmoji"

interface CountryFlagEmojiProps {
  code?: CountryCode
}

export const CountryFlagEmoji = ({ code }: CountryFlagEmojiProps) => {
  const title = code ? countryNameRecord[code] || code : undefined
  return (
    <span role="img" aria-labelledby={title} title={title}>
      {code ? getCountryFlagEmoji(code) : "🏳"}
    </span>
  )
}
Enter fullscreen mode Exit fullscreen mode

The CountryFlagEmoji component receives a single property - a country code. We make this parameter optional to accommodate scenarios where we need a flag shape placeholder in our UI. If the country code is present, we take the country's name from the countryNameRecord and pass it to aria-labelledby for accessibility purposes, also to the title attribute for the tooltip. We use the getCountryFlagEmoji function to generate a flag emoji from the country code.

import { CountryCode } from "./countryNameRecord"

export const getCountryFlagEmoji = (countryCode: CountryCode) => {
  const codePoints = countryCode
    .toUpperCase()
    .split("")
    .map((char) => 127397 + char.charCodeAt(0))
  return String.fromCodePoint(...codePoints)
}
Enter fullscreen mode Exit fullscreen mode

We can now iterate over every country present in our record and evaluate the efficiency of an emoji flag as a solution.

CountryFlagEmoji

Flag emojis are relatively effortless to implement, and they cover every country. However, they tend to appear differently across different platforms. This is the primary reason I opted for SVG flags when building the scoreboard with the most productive users for my productivity app - Increaser.

Use SVG to Display a Flag

The CountryFlag component receives the same code property as our emoji component. Yet, it also accepts an optional path to hosted SVG files. I employ a NextJS app, therefore I store all flags in /images/flags within the public folder. I located these flags in the flag-icons GitHub repo and copied them to my project.

import styled from "styled-components"
import { SafeImage } from "../ui/SafeImage"
import { CoverImage } from "../ui/images/CoverImage"
import { getColor } from "../ui/theme/getters"
import { centerContentCSS } from "../ui/utils/centerContentCSS"
import { UIComponentProps } from "../props"
import {
  CountryCode,
  countryNameRecord,
} from "@reactkit/utils/countryNameRecord"

interface CountryFlagProps extends UIComponentProps {
  code?: CountryCode
  source?: string
}

const Wrapper = styled.div`
  aspect-ratio: 4 / 3;
  background: ${getColor("mist")};
  overflow: hidden;
  ${centerContentCSS};
`

export const CountryFlag = ({
  code,
  source = "/images/flags",
  ...props
}: CountryFlagProps) => {
  return (
    <Wrapper
      title={code ? countryNameRecord[code] || code : undefined}
      {...props}
    >
      {code && (
        <SafeImage
          src={code ? `${source}/${code.toLowerCase()}.svg` : undefined}
          render={(props) => (
            <CoverImage
              alt={countryNameRecord[code] || code}
              title={countryNameRecord[code] || code}
              {...props}
            />
          )}
        />
      )}
    </Wrapper>
  )
}
Enter fullscreen mode Exit fullscreen mode

In order to outline the shape of a flag we render it inside a Wrapper, which is a component with a 4/3 aspect ratio. The Wrapper also has a mist background, hidden overflow, and flex display with centered content.

import { ReactNode } from "react"
import { useBoolean } from "../shared/hooks/useBoolean"

interface RenderParams {
  src: string
  onError: () => void
}

interface Props {
  src?: string
  fallback?: ReactNode
  render: (params: RenderParams) => void
}

export const SafeImage = ({ fallback = null, src, render }: Props) => {
  const [isFailedToLoad, { set: failedToLoad }] = useBoolean(false)

  return (
    <>
      {isFailedToLoad || !src
        ? fallback
        : render({ onError: failedToLoad, src })}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

To prevent a broken image scenario in the event of a failed load, we employ the SafeImage wrapper. This will restrain from rendering the image if an error is detected.

import styled from "styled-components"

export const CoverImage = styled.img`
  width: 100%;
  height: 100%;
  object-fit: cover;
`
Enter fullscreen mode Exit fullscreen mode

The CoverImage component occupies the entire width and height of its parent. It applies object-fit: cover to ensure full area coverage by the image.

CountryFlag

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay