기본적으로는 그냥 텍스트로 보이지만, 클릭하면 수정할 수 있는 텍스트 박스를 만들어 보자!
우선 필요한 기능은 다음과 같이 간단하다.
1. editable 상태에 따라 plain text / input box로 표시되어야 한다.
2. 엔터 버튼을 누르거나, 컴포넌트 외부를 클릭하면 editable이 false가 되어야 한다.
import React, { useState } from "react";
interface TextInputProps {
init: string;
}
function TextInput({ init }: TextInputProps) {
const [text, setText] = useState(init);
const [editable, setEditable] = useState(false);
}
컴포넌트 내에 만들어줄 state는 text와 editable 두 개다.
text는 input box에서 우리가 입력하는 텍스트를 반영해주고, editable은 컴포넌트를 plain text로 표시할 것인지, input box로 표시할 것인지를 담고 있다.
text에 기본으로 들어가는 init은 props로 줄 기본 텍스트다.
아래부터는 TextInput function 안에 들어가는 부분이다.
const editOn = () => {
setEditable(true);
};
const handleChange = (e) => {
setText(e.target.value);
};
const handleKeyDown = (e) => {
if (e.key === "Enter") {
setEditable(!editable);
}
};
editOn은 text 상태일 때 onClick 이벤트로 넣어줄 함수다. editable을 true로 만들어준다.
handleChange는 input 상태일 때 내용의 변화를 감지해서 text를 바꾸어준다. 이게 없으면 input 상태인데도 수정이 되지 않는다.
handleKeyDown은 enter키를 눌렀을 때 입력을 중지하는 함수다.
return (
<>
{editable ? (
<input type="text" value={text} onChange={(e) => handleChange(e)} onKeyDown={handleKeyDown} />
) : (
<div onClick={() => editOn()}>{text}</div>
)}
</>
);
조건부 렌더링으로, editable의 값에 따라 input 혹은 text를 리턴해준다.
조건식은 (변수명)? (true일 때 반환값) : (false일 때 반환값)의 형식으로 써주는데, 이 경우 editable ? input : text인 것이다.
필요한 기능의 대다수는 이렇게 쉽게 구현이 가능하다. 그런데 '컴포넌트 외부를 클릭하면 editable이 false가 되'도록 하는 것은 조금 더 신경을 써야 한다.
우선 react에서 useRef, useEffect를 import해주자.
useEffect(() => {
window.addEventListener("click", handleClickOutside, true);
});
TextInput 컴포넌트가 함수형 컴포넌트이기 때문에, componentDidMount()를 쓸 수 없어서 대신 useEffect hook을 써주는 것이다. useEffect 안에 window.addEventListener를 넣어주자. 그러면 이 친구는 window에서 클릭 이벤트가 일어날 때마다 handleClickOutside를 실행할 것이다.
const ref = useRef(null);
그 전에 ref를 선언해둔다. useRef도 hook인데, 만들어둔 ref를 <div ref={ref} /> 처럼 전달하면 ref.current로 해당 자바스크립트 객체에 접근할 수 있다. 더 자세한 개념 설명은 공식 문서를..🙄
왜 useRef를 쓰냐? 해당 컴포넌트 외부에서 클릭을 했는지 체크하기 위해서이다.
const handleClickOutside = (e) => {
if (editable == true && !ref.current.contains(e.target)) setEditable(false);
};
ref.current에는 현재 자바스크립트 객체가 들어갈 것이고, 현재 객체가 e.target 즉 지금 클릭한 녀석을 포함하고 있지 않으면 editable을 false로 해준다. 즉 그냥 클릭한 게 현재 객체가 아니면 edit모드를 꺼주는 것이다.
import React, { useState, useEffect, useRef } from "react";
interface TextInputProps {
init: string;
}
function TextInput({ init }: TextInputProps) {
const ref = useRef(null);
const [text, setText] = useState(init);
const [editable, setEditable] = useState(false);
const editOn = () => {
setEditable(true);
};
const handleChange = (e) => {
setText(e.target.value);
};
const handleKeyDown = (e) => {
if (e.key === "Enter") {
setEditable(!editable);
}
};
const handleClickOutside = (e) => {
if (editable == true && !ref.current.contains(e.target)) setEditable(false);
};
useEffect(() => {
window.addEventListener("click", handleClickOutside, true);
});
return (
<>
<div ref={ref}>
{editable ? (
<input type="text" value={text} onChange={(e) => handleChange(e)} onKeyDown={handleKeyDown} />
) : (
<div onClick={() => editOn()}>{text}</div>
)}
</div>
</>
);
}
export default TextInput;
총 코드를 확인해 보면 이렇게 생겼다. 물론 위의 사진은 저기에 props와 style를 좀 줘서 꾸민 것이다😎
'Web > Frontend' 카테고리의 다른 글
[리액트] 토스트 메세지 만들기 (1) | 2020.10.26 |
---|---|
[리액트] 이미지 업로더 - 서버에 이미지 Post로 보내기 (2) | 2020.07.20 |
[리액트] 드래그 앤 드롭, 리사이징 리스트 만들기 (2) | 2020.06.02 |
[리액트] immutable-js 사용해보기 (2) | 2020.05.21 |
[리액트] TypeScript, Styled-Components 프로젝트 초기 셋팅 (1) | 2020.05.14 |
댓글