iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0

Day06 要做的是動態搜尋功能,是個可以偵測使用者輸入字串的篩選器

建立資料

  • 資料可以由 fetch 遠端取得

  • searchQuery 用來儲存搜尋字串

  • suggestions 是建議

const URL =
  "https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json";

type City = {
  city: string;
  growth_from_2000_to_2013: string;
  latitude: number;
  longitude: number;
  population: string;
  rank: string;
  state: string;
};

  const [cities, setCities] = useState<City[]>(() => {
    fetch(URL)
      .then((res) => res.json())
      .then((data: City[]) => setCities(data))
      .catch((error) => console.log("Failed to fetch cities:", error));
    return [];
  });

  const [searchQuery, setSearchQuery] = useState<string>("");
  const [suggestions, setSuggestions] = useState<City[]>([]);
  • 加上 loading 判斷
  if (cities.length === 0) {
    return <div>Loading...</div>;
  }

處理輸入變化

  • 用了正規表達式來判斷資料有沒有符合
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    const value = e.target.value;
    setSearchQuery(value);

    if (!value) {
      setSuggestions([]);
      return;
    }
    const matches = cities.filter((place) => {
      const regex = new RegExp(value, "gi");
      return place.city.match(regex) || place.state.match(regex);
    });

    setSuggestions(matches);
  };

樣式

const evenItemStyle: React.CSSProperties = {
  background: "linear-gradient(to bottom, #ffffff 0%, #EFEFEF 100%)",
  transform: "perspective(100px) rotateX(3deg) translateY(2px) scale(1.001)",
};

const oddItemStyle: React.CSSProperties = {
  background: "linear-gradient(to top, #ffffff 0%, #EFEFEF 100%)",
  transform: "perspective(100px) rotateX(-3deg) translateY(3px)",
};

畫面結構

  return (
    <div className="box-border bg-gradient-to-r from-yellow-300 via-yellow-400 to-orange-400 text-xl font-light min-h-screen py-10">
      <form className="max-w-[320px] mx-auto">
        <input
          type="text"
          className="w-[120%] -left-[10%] relative top-2 z-10 p-5 m-0 text-center outline-none border-[10px] border-gray-100 rounded-[5px] text-4xl shadow-[0_0_5px_rgba(0,0,0,0.12),inset_0_0_2px_rgba(0,0,0,0.19)]"
          placeholder="City or State"
          value={searchQuery}
          onChange={handleInputChange}
        />
        <ul className="m-0 p-0 relative">
          {!suggestions.length && !searchQuery && (
            <>
              <li
                className="bg-white list-none border-b border-gray-300 shadow-[0_0_10px_rgba(0,0,0,0.14)] m-0 p-5 transition-colors duration-200 flex justify-between capitalize"
                style={evenItemStyle}
              >
                Filter for a city
              </li>
              <li
                className="bg-white list-none border-b border-gray-300 shadow-[0_0_10px_rgba(0,0,0,0.14)] m-0 p-5 transition-colors duration-200 flex justify-between capitalize"
                style={oddItemStyle}
              >
                or a state
              </li>
            </>
          )}
          {suggestions.map((place, index) => (
            <li
              key={place.city}
              className="bg-white list-none border-b border-gray-300 shadow-[0_0_10px_rgba(0,0,0,0.14)] m-0 p-5 transition-colors duration-200 flex justify-between capitalize"
              style={index % 2 === 0 ? evenItemStyle : oddItemStyle}
            >
              <span>
                {highlightMatch(`${place.city}, ${place.state}`, searchQuery)}
              </span>

              <span className="text-sm">
                {numberWithCommas(place.population)}
              </span>
            </li>
          ))}
        </ul>
      </form>
    </div>
  );

處理字串的函式

  • 用正規表達式將數字加上 commas
const numberWithCommas = (x: string): string =>
  String(x).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  • 用正規表達式將符合的字元塗上螢光筆背景色
const highlightMatch = (
  text: string,
  match: string
): (string | JSX.Element)[] => {
  const regex = new RegExp(`(${match})`, "gi");

  return text.split(regex).map((part) =>
    regex.test(part) ? (
      <span key={part} className="bg-yellow-400">
        {part}
      </span>
    ) : (
      part
    )
  );
};

DEMO

https://codesandbox.io/p/devbox/gnvrkw

總結

  • 這集學到了建立正規表達式的用法
const regex = new RegExp(value, "gi");
  • 用正規表達式將數字加上 commas
const numberWithCommas = (x: string): string =>
  String(x).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  • 利用 CSS 畫出 3D 效果
  transform: "perspective(100px) rotateX(-3deg) translateY(3px)",

上一篇
[Day05]_Flex-Panel-Gallery
下一篇
[Day07]_Array-Cardio-Day2
系列文
React30——用 React 探索 JavaScript30 的魅力30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言