Post

프리온보딩 사전 미션 (검색어와 일치하는 글자 하이라이트)

24년 8월 프리온보딩 사전미션

question

위 영상과 동일한 기능의 화면을 만들기를 한 기록

구현 내용

  1. Input 박스, 검색버튼
  2. 검색어를 입력했을 때 dummy data를 보여주기
  3. 검색어와 일치하는 dummy data text에 하이라이트하기

결과물

입력값이 없을 때

스크린샷 2024-07-18 오후 12 11 52

a를 입력했을 때

스크린샷 2024-07-18 오후 12 11 57

apple inc(특정 단어)를 입력했을 때

스크린샷 2024-07-18 오후 12 12 08

코드

1. Input 박스, 검색버튼 등 UI 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
return (
  <>
    <div id="main">
      <form className="search-form">
        <input
          className="search-input"
          type="text"
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
        />

        <button type="submit">검색</button>
      </form>


      <--- dummy data 내용 --->
    </div>
  </>
);

3. dummy data를 type을 기준으로 초기화하기

1
2
3
4
5
6
7
8
9
10
const categorized = dummy.reduce((acc: Categorized, item: DummyItem) => {
  const { type } = item;
  if (!acc[type]) {
    acc[type] = [];
  }

  acc[type].push(item);

  return acc;
}, []);

4. dummy data item의 text가 검색어와 일치하면 하이라이트하기

  • 검색어(hightlight)를 기준으로 text을 나눔
  • 검색어(hightlight)와 일치하는 문자는 <b> 태그를 사용해서 굵은 문자로 반환
  • 일치하지 않는 문자는 그냥 리턴
1
2
3
4
5
6
const handleHighlightedText = (text: string, highlight: string) => {
  const splitText = text.split(new RegExp(`(${highlight})`, "gi"));
  return splitText.map((t, index) =>
    t.toLowerCase() === highlight.toLowerCase() ? <b key={index}>{t}</b> : t
  );
};

5. 위의 기능을 추가한 dummy data UI

  • 검색어를 입력하지 않았을 때는 보여주지 않음
  • dummy data의 type별로 내용을 구분해서 화면에 출력
  • handleHighlightedText함수를 사용해서 검색어와 일치하는 내용 하이라이트 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  searchTerm.length > 0 && (
    <div className="dummy-data-container">
      {Object.keys(categorized).map((key) => (
        <>
          <div className="dummy-data-type">{key}</div>
          <ul className="dummy-data-item-container">
            {categorized[key].map((item: DummyItem) => (
              <li key={item.key} className="dummy-data-item">
                {handleHighlightedText(item.description, searchTerm)}
              </li>
            ))}
          </ul>
        </>
      ))}
    </div>
  );
}

전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { useState } from "react";
import "./App.css";

// @ts-expect-error: TypeScript declaration file for 'data.js' is missing
import { dummy } from "./data.js";

interface DummyItem {
  description: string;
  key: string;
  type: string;
}

interface Categorized {
  [key: string]: DummyItem[];
}

function App() {
  const [searchTerm, setSearchTerm] = useState < string > "";
  const categorized = dummy.reduce((acc: Categorized, item: DummyItem) => {
    const { type } = item;
    if (!acc[type]) {
      acc[type] = [];
    }

    acc[type].push(item);

    return acc;
  }, []);

  const handleHighlightedText = (text: string, highlight: string) => {
    const splitText = text.split(new RegExp(`(${highlight})`, "gi"));
    return splitText.map((t, index) =>
      t.toLowerCase() === highlight.toLowerCase() ? <b key={index}>{t}</b> : t
    );
  };

  return (
    <>
      <div id="main">
        <form className="search-form">
          <input
            className="search-input"
            type="text"
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.target.value)}
          />

          <button type="submit">검색</button>
        </form>

        {searchTerm.length > 0 && (
          <div className="dummy-data-container">
            {Object.keys(categorized).map((key) => (
              <>
                <div className="dummy-data-type">{key}</div>
                <ul className="dummy-data-item-container">
                  {categorized[key].map((item: DummyItem) => (
                    <li key={item.key} className="dummy-data-item">
                      {handleHighlightedText(item.description, searchTerm)}
                    </li>
                  ))}
                </ul>
              </>
            ))}
          </div>
        )}
      </div>
    </>
  );
}

export default App;
This post is licensed under CC BY 4.0 by the author.