
react-arborist로 트리 편집 UI를 구현할 때 한글 입력 시, ㅇ 같은 초성이 중복 입력되거나 스페이스가 입력되지 않는 문제가 발생했다.
문제 원인
1. 키 이벤트 버블링으로 트리 단축키가 개입한다.
react-arborist는 space, arrow 등을 트리 토글/이동 단축키로 사용한다. 입력 필드에서 발생한 키 이벤트가 상위(Tree)로 버블링되면 공백 입력 대신 트리 동작이 실행된다. 스페이스가 안 찍히거나 포커스가 의도치 않게 움직인다.
2. Controlled Input이 부모 리렌더를 유발해 IME 조합을 끊는다.
입력값을 부모 상태에 직접 바인딩하면 매 키 입력마다 부모가 리렌더된다. 가상 리스트(트리 행)까지 재렌더, 리마운트되면서 커서 위치가 초기화되고, IME 조합이 중간에 끊겨 중복 입력 현상이 발생한다.
해결 방법
1. 키 이벤트 버블링을 “캡처 단계”에서 차단한다.
입력 컴포넌트에서 캡처 단계로 전파를 막아 트리 단축키가 개입하지 못하게 한다.
<Input
onKeyDownCapture={(e) => e.stopPropagation()}
onKeyUpCapture={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()} // 행 토글 방지
/>
2. 입력은 “로컬 state로만” 관리하고, 커밋 시에만 부모로 올린다.
(기존) 부모가 직접 제어하는 방식은 문제를 유발한다. 위에서 살펴봤듯이, 전체 트리가 재렌더링 되기 때문이다.
// 부모 컴포넌트
const [draftName, setDraftName] = useState(''); // 부모가 입력값을 보관
{isEditing ? (
<Inputvalue={draftName} // 매 키 입력마다 부모 상태 변경
onChange={(e) => setDraftName(e.target.value)} // → 부모 리렌더 → 행 재마운트 → 커서/IME 깨짐
onKeyDown={(e) => {
if (e.key === 'Enter') { e.preventDefault(); commitEdit(); }
}}
/>
) : (
<span>{node.data?.name}</span>
)}
(해결) 로컬 state 별도 관리 및 커밋 시 부모에 반영한다.
function EditableNameInput({
initial,
onCommit,
onCancel,
}: { initial: string; onCommit: (v: string) => void; onCancel: () => void }) {
const [val, setVal] = useState(initial);
const [isComposing, setIsComposing] = useState(false);
return (
<Inputvalue={val} // 입력은 로컬에서만 관리
onChange={(e) => setVal(e.target.value)}
onKeyDownCapture={(e) => e.stopPropagation()} // 트리 단축키 차단
onKeyUpCapture={(e) => e.stopPropagation()}
onKeyPressCapture={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
onCompositionStart={() => setIsComposing(true)} // IME 안전
onCompositionEnd={() => setIsComposing(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (isComposing) return; // 조합 중 커밋 금지
e.preventDefault();
onCommit(val.trim());
}
if (e.key === 'Escape') { e.preventDefault(); onCancel(); }
}}
/>
);
}
// 부모는 커밋 시점에만 트리를 갱신한다
const commitEdit = (next: string) => {
if (!next) { alert('이름을 입력한다.'); return; }
setModifiedDataWarehousesData(prev => updateNodeName(prev, editingId!, next));
setEditingId(null);
};
입력을 부모 상태에 직접 바인딩해 전체 컴포넌트가 매번 재렌더링되면, 예상치 못한 사이드 이펙트가 발생할 수 있다는 사실을 배웠다.
'Frontend' 카테고리의 다른 글
| key로 컴포넌트 안의 state를 초기화 하기 (0) | 2025.12.07 |
|---|---|
| React Query에서 두 번째 요청이 더 빠르게 느껴지는 이유 (0) | 2025.12.07 |
| React-Arborist 트리 UI에서 Input 포커스 문제 해결 (0) | 2025.08.23 |
| eslint 설정해보기 (5) | 2025.07.08 |
| 모노레포 구축하기 (1) | 2025.07.08 |