[JavaScript] 조잡한 검색 리스트 만들기로 구현한 결과물을 Next.js
로 옮겼다.
데이터로 사용한 배열:
const fruits = ["apple","banana","orange","lemon","lime","pure","peach","berry","dorian","mango","starfruit","dragonFruit","almond","walnut","grape","persimmon"]
input
의 변화를 감지하고 데이터를 상태에 저장하는 기본 코드이다. TypeScript
환경이라 변화할 때의 이벤트 인자를 얻기 위해 ChangeEventHandler<HTMLInputElement>
타입을 할당했다.
import { useState } from "react";
import type { ChangeEventHandler } from "react";
export default function Home() {
const fruits = [...];
const [isHidden, setIsHidden] = useState(true);
const [result, setResult] = useState("");
const [search, setSearch] = useState("");
const onChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const { value } = e.currentTarget;
setSearch(value);
};
const onResetClick = () => {
setResult("");
setSearch("");
};
return (
<main className="container">
<section>
<input
type={"search"}
onChange={onChange}
value={search}
/>
<ul hidden={isHidden}>
{fruits.map((fruit, idx) => (
<li key={idx} style={{ cursor: "pointer" }}>
{fruit}
</li>
))}
</ul>
<button onClick={onResetClick}>reset</button>
</section>
<div>{result}</div>
</main>
);
}
Focus In/Out
바닐라 JS
의 focus
이벤트는 focusin
과 focusout
이지만, jsx
에서는 onFocus
와 onBlur
에 매칭된다.
import type { FocusEventHandler } from "react";
function Home() {
const onFocusIn: FocusEventHandler<HTMLInputElement> = (e) => {
setIsHidden(false);
};
const onFocusOut: FocusEventHandler<HTMLInputElement> = (e) => {
setIsHidden(true);
};
return (
<section>
<input
onFocus={onFocusIn}
onBlur={onFocusOut}
/>
<ul hidden={isHidden}>
{/* (...) */}
</ul>
</section>
);
};
input
에 포커스가 잡히면(onFocus
) ul
은 노출되고, 벗어나면(onBlur
) ul
은 다시 숨는다. 삼항 연산자
를 사용하면 요소를 초기화면에 렌더링하지 않을 수 있다.
{!isHidden ? (
<ul>
{fruits.map((fruit, idx) => (
<li
key={idx}
onMouseOver={onMouseOver}
onMouseLeave={onMouseLeave}
onClick={onAddResultClick}
style={{ cursor: "pointer" }}
hidden={!fruit.includes(search)}
>
{fruit}
</li>
))}
</ul>
) : null}
Mouse Over/Leave
마우스 이벤트이기에 MouseEventHandler<HTMLLIElement>
를 할당했다.
import { useState } from "react";
import type { MouseEventHandler } from "react";
function Home() {
const [liOver, setLiOver] = useState(false);
const onMouseOver: MouseEventHandler<HTMLLIElement> = (e) => {
setLiOver(true);
e.currentTarget.style.background = "pink";
};
const onMouseLeave: MouseEventHandler<HTMLLIElement> = (e) => {
setLiOver(false);
e.currentTarget.style.background = "none";
};
return (
<section>
{/* (...) */}
<ul hidden={isHidden}>
{fruits.map((fruit, idx) => (
<li
key={idx}
onMouseOver={onMouseOver}
onMouseLeave={onMouseLeave}
style={{ cursor: "pointer" }}
hidden={!fruit.includes(search)}
>
{fruit}
</li>
))}
</ul>
</section>
);
};
검색한 값을 포함하지 않은 li
는 hidden
으로 숨겼다.
[JavaScript] 조잡한 검색 리스트 만들기에서는 하위 요소 클릭 시 focusout
이 먼저 동작하여 클릭이 안 되는 현상을 어렵게 해결했다. 여기서는 한 줄 추가로 상위 요소의 이벤트를 방지했다.
function Home() {
const onFocusOut: FocusEventHandler<HTMLInputElement> = (e) => {
if (liOver) return;
setIsHidden(true);
};
const onAddResultClick: MouseEventHandler<HTMLLIElement> = (e) => {
const { textContent } = e.currentTarget;
setResult(textContent as string);
setIsHidden(true);
};
return (
<section>
{/* (...) */}
<ul hidden={isHidden}>
{fruits.map((fruit, idx) => (
<li
key={idx}
onClick={onAddResultClick}
>
{fruit}
</li>
))}
</ul>
</section>
);
};
마우스를 올렸을 때 liOver
가 true
상태이고, 그럴 때 onFocusOut
을 얼리 리턴(early return)
하여 setIsHidden
동작을 막았다.
import { useState } from "react";
import type {
MouseEventHandler,
ChangeEventHandler,
FocusEventHandler,
} from "react";
export default function Home() {
const fruits = ["apple","banana","orange","lemon","lime","pure","peach",
"berry","dorian","mango","starfruit","dragonFruit",
"almond","walnut","grape","persimmon"]
const [isHidden, setIsHidden] = useState(true);
const [liOver, setLiOver] = useState(false);
const [result, setResult] = useState("");
const [search, setSearch] = useState("");
const onChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const { value } = e.currentTarget;
setSearch(value);
};
const onFocusIn: FocusEventHandler<HTMLInputElement> = (e) => {
setIsHidden(false);
};
const onFocusOut: FocusEventHandler<HTMLInputElement> = (e) => {
if (liOver) return;
setIsHidden(true);
};
const onResetClick = () => {
setResult("");
setSearch("");
};
const onMouseOver: MouseEventHandler<HTMLLIElement> = (e) => {
setLiOver(true);
e.currentTarget.style.background = "pink";
};
const onMouseLeave: MouseEventHandler<HTMLLIElement> = (e) => {
setLiOver(false);
e.currentTarget.style.background = "none";
};
const onAddResultClick: MouseEventHandler<HTMLLIElement> = (e) => {
const { textContent } = e.currentTarget;
setResult(textContent as string);
setIsHidden(true);
};
return (
<main className="container">
<section>
<input
type={"search"}
onFocus={onFocusIn}
onBlur={onFocusOut}
onChange={onChange}
value={search}
/>
<ul hidden={isHidden}>
{fruits.map((fruit, idx) => (
<li
key={idx}
onMouseOver={onMouseOver}
onMouseLeave={onMouseLeave}
onClick={onAddResultClick}
style={{ cursor: "pointer" }}
hidden={!fruit.includes(search)}
>
{fruit}
</li>
))}
</ul>
<button onClick={onResetClick}>reset</button>
</section>
<div>{result}</div>
</main>
);
}