본문 바로가기
Web/Frontend

[리액트] 수정 가능한 텍스트 박스 만들기

by r4bb1t 2020. 5. 25.
반응형

기본적으로는 그냥 텍스트로 보이지만, 클릭하면 수정할 수 있는 텍스트 박스를 만들어 보자!

우선 필요한 기능은 다음과 같이 간단하다.

 

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를 좀 줘서 꾸민 것이다😎

반응형

댓글