DEV Community

Cover image for Building a Drop-Down-List with Country-Code + Flag + Telephone
Mark
Mark

Posted on • Edited on

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.

Image description

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.

Image description

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`}
/>


Enter fullscreen mode Exit fullscreen mode

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]);


Enter fullscreen mode Exit fullscreen mode

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 does not work 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>


Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

webpack config



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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode


// webpack.config.js
module.exports = {
  //...
  module: {
    rules: [
      //...
      {
        test: /\.svg$/,
        use: 'raw-loader',
      },
    ],
  },
  //...
};



Enter fullscreen mode Exit fullscreen mode


// webpack.config.js
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        svg: {
          test: /\.svg$/,
          name: 'svg',
          chunks: 'all',
          enforce: true
        }
      }
    }
  },
  //...
};


Enter fullscreen mode Exit fullscreen mode

Now there is only 1 request for all svg files.

Image description

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

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

Top comments (0)