Building a Drop-Down-List with Country-Code + Flag + Telephone

Building a Drop-Down-List with Country-Code + Flag + Telephone

In this article I will build a React component for telephone input with country code and flag drop-down-list as prefix select.

When you click the flag part, the drop-down-list will show, and you can search country use code or country name, when you click the country item, it will change current country.

I searched on the internet to find country code and flag data, finally, I get a json file contains country data.

https://github.com/Dwarven/Phone-Country-Code-and-Flags/blob/master/Phone-Country-Code-and-Flags.bundle/phone_country_code.json

This json file will be used to build my component, but I don’t like the flag image in this project, It is one png file contains all flag, you have to use CSS Sprite, but I don’t know the position of the country. What I want is svg flag file of every country.

Fortunately, I found flag svg files in this project.

https://github.com/necessarylion/country-list-with-dial-code-and-flag/tree/master/assets/svg

Then I picked the json file and svg files into my project.

The first version is straight forward. You can checkout the code, and run it.

https://github.com/markliu2013/react-phone-country-code-flag/tree/v1

But there is performance problem now, you have to wait for all svg files loaded before component mount.

So I am thinking how to use lazy load technology, the svg files start to load when you click the flag icon, not before mount. and it only load about 6 svg files in the viewport area, the other svg files start to load when you scroll to show in the viewport.

You can use Intersection Observer API to implement svg file lazy load easily.

Firstly, change img src to use data-src.

<img
className=“phone-country-select-list-flag”
data-src={`flags/${item.countryCode}.svg`}
/>

When observer load svg.

// flag icon lazy load
const [flagLoaded, setFlagLoaded] = useState(false);
function flagLazyLoad() {
// should run only once.
if (flagLoaded) return;
const callback = (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const image = entry.target.querySelector(.phone-country-select-list-flag);
const data_src = image.getAttribute(data-src);
image.setAttribute(src, data_src);
observer.unobserve(entry.target);
}
});
};
const observer = new IntersectionObserver(callback, {
root: document.querySelector(.phone-country-select-dropdown ul)
});
const images = document.querySelectorAll(.phone-country-select-dropdown ul li);
images.forEach((image) => {
observer.observe(image);
});
setFlagLoaded(true);
}

useEffect(() => {
if (isOpen) {
flagLazyLoad();
}
}, [isOpen]);

You can checkout branch v2 for full code. https://github.com/markliu2013/react-phone-country-code-flag/tree/v2

Now we can accept the performance, but it is not that user friendly, the country flag is blank when you scroll, it have to wait seconds to load.

Let’s think how to make it the better.

SVG file is actually html format, You can read svg content and put them in html page directly.

<!DOCTYPE html>
<html>
<body>

<svg width=“400” height=“180”>
<rect x=“50” y=“20” rx=“20” ry=“20” width=“150” height=“150”
style=“fill:red;stroke:black;stroke-width:5;opacity:0.5” />
Sorry, your browser does not support inline SVG.
</svg>

</body>
</html>

https://www.w3schools.com/html/tryit.asp?filename=tryhtml_svg_rect_round

If we can use webpack to generate a bundle file for every SVG files, that means we can make all svg files into a bundle file, then we can read content from that bundle file, and write svg content into the page directly. If so, we can use only 1 request to load all flag data.

Firstly, create FlagSvg component.

import React, { useEffect, useState } from react;

const FlagSvg = ({ code }) => {
const [data, setData] = useState();

useEffect(() => {
const fetchData = async () => {
const svgUrl = await import(`./flags/${code}.svg?raw`);
// const response = await fetch(svgUrl.default);
// const content = await response.text();
setData(svgUrl.default);
};
fetchData().catch(console.error);
}, [code]);

return (
<div className=phone-country-select-list-flag dangerouslySetInnerHTML={{ __html: data }} />
);
};

export default FlagSvg;

Use it in the list.

<ul>
{countryList.map((item, index) => (
<li
key={index}
onClick={() => itemClickHandler(item)}
className={item.visible ? ‘show’ : ‘hide’}
>
<FlagSvg code={item.countryCode} />
<span className=“phone-country-select-list-name”>{item.name}</span>
<span className=“phone-country-select-list-code”>{item.code}</span>
</li>
))}
</ul>

webpack config

{
test: /.svg/,
type: asset/resource,
generator: {
filename: images/[hash][ext][query]
}

Check out this commit:
https://github.com/markliu2013/react-phone-country-code-flag/commit/c99ce806655055f1c4c6face6a985dc6c24bac1b

it still works well, but there is many request for flags.

Now we will try to make a bundle file for svg files.

npm install –save-dev raw-loader
// webpack.config.js
module.exports = {
//…
module: {
rules: [
//…
{
test: /.svg$/,
use: raw-loader,
},
],
},
//…
};
// webpack.config.js
module.exports = {
//…
optimization: {
splitChunks: {
cacheGroups: {
svg: {
test: /.svg$/,
name: svg,
chunks: all,
enforce: true
}
}
}
},
//…
};

Now there is only 1 request for all svg files.

Checkout the full code: https://github.com/markliu2013/react-phone-country-code-flag

Vite3 + React + TypeScript version: https://github.com/markliu2013/react-phone-country