contentEditable | React-hook-form

Bori·2023년 6월 3일
4

어쨌든 공부

목록 보기
18/41

contentEditable

contentEditable은 *전역 특성으로 요소 내의 텍스트나 컨텐츠를 편집할 수 있도록 만들어주는 HTML 속성입니다.

  • 'true' 또는 빈 문자열 : 요소에 텍스트를 입력하거나 편집할 수 있습니다.
  • 'false' : 요소를 편집할 수 없습니다.
  • 'inherit' : 부모 요소의 contentEditable 속성을 상속받습니다.

전역 특성(Global attributes)

  • 모든 HTML에서 공통으로 사용할 수 있는 특성으로 모든 HTML 요소에 지정할 수 있습니다.
  • 하지만, 일부 요소에는 아무런 효과도 없을 수 있습니다.

contentEditable를 적용하면 input이나 textarea처럼 텍스트를 입력할 수 있습니다!
input이나 textarea를 사용하면 기본 스타일을 제거해야하고, 한 줄이 아니라 여러 줄을 입력할 경우 텍스트 높이에 따라 입력 영역의 높이도 높여줘야 하는 번거로움이 있는데, contentEditable을 적용할 경우 이러한 불편함이 사라집니다.

contentEditable 적용하기

적용하는 방법은 간단합니다. 아래와 같이 적용하면 h1 태그의 컨텐츠를 편집할 수 있게 됩니다.

<h1 contentEditable>게시글 제목</h1>

그리고 다음과 같은 경고 메시지도 함께 등장합니다. 쨔쟌~

React에서 contentEditable 속성을 가진 요소가 있을 때 나타나는 경고 메시지 입니다.
contentEditable 속성을 적용하면 해당 요소는 사용자가 직접 컨텐츠를 편집할 수 있기 때문에 React가 해당 요소를 제어할 수 없게 됩니다. 따라서, React에 의해 렌더링 될 경우 React의 가상 DOM과 실제 DOM 사이에 충돌이 일어날 수 있기 때문에 React는 경고 메시지를 나타냅니다.

해당 경고를 무시하려면 suppressContentEditableWarning 속성을 추가합니다.

<h1 contentEditable suppressContentEditableWarning={true}>게시글 제목</h1>

하지만 이 방법은 경고 메시지를 무시하는 속성일 뿐 예기치 못한 문제를 해결해주는 방법은 아니라는 것을 알고 사용하는 것이 좋습니다.

placeholder 적용하기

일반적으로 form 요소를 사용하게 되면 placeholder 속성을 적용하여 해당 입력 필드에 사용자가 입력해야할 값의 형식이나 예시를 나타냅니다.
contentEditable 속성을 적용하게 되면 placeholder 를 직접 구현해야 합니다.

먼저, 다음과 같이 placeholder로 보여질 메시지를 input에서 적용하는 방식과 동일하게 작성했습니다.

<Title 
  contentEditable 
  suppressContentEditableWarning={true}
  placeholder="제목을 작성해주세요."
/>

그리고 다음과 같이 스타일을 적용합니다.

// emotion을 이용한 코드
const Title = styled.h1`
  &:empty::before {
    content: attr(placeholder);
    color: #CCC;
  }
`;

:empty는 가상 클래스로 해당 요소의 자식 요소나 텍스트가 없다면 지정한 스타일이 나타납니다. 단, 공백, 개행, 주석 등이 포함되어 있다면 :empty 선택자와 일치하지 않습니다.

:empty를 이용하여 해당 요소에 텍스트가 입력되어 있지 않을 경우, placeholder에 입력한 메시지가 옅은 회색으로 나타나게 했습니다.

아래는 적용한 모습입니다.
제목은 h1 태그에 contentEditable 속성을 적용했고, 내용은 textarea로 작성한 코드입니다. 감쪽같죠?

placeholder텍스트 입력

with React-hook-form

이제 React-hook-form 을 적용해봅시다!

form 요소가 아닌데 적용할 수 있을까요? 네

하지만 contentEditable 요소는 onChange를 사용할 수 없습니다. 대신 onInput를 사용할 수 있습니다.
register의 메서드에는 onInput가 없기 때문에 다음과 같이 적용할 수 있습니다.

*본 내용에서 React-hook-form 사용 방법은 설명하지 않습니다. 간단한 설명이라도 괜찮다면 링크를 참고해주세요.
const { register, setValue } = useForm<{ title: string }>({ mode: 'onChange' });

const handleOnInput: FormEventHandler<HTMLHeadingElement> = (e) => {
  const { textContent } = e.currentTarget;
  if (textContent !== null) {
    // heading 요소에 입력된 textContent를 title의 값으로 넣어준다.
    setValue('title', `${textContent}`, {
      shouldValidate: true, // 유효성을 검사하도록 설정
    });
  }
};

const onSubmit: SubmitHandler<{ title: string }> = (data) => {
  console.log(data);
};

<form onSubmit={handleSubmit(onSubmit)}>
  <Title 
    contentEditable 
    suppressContentEditableWarning={true}
    placeholder="제목을 작성해주세요."
    {...register('title', {
      required: true,
    })}
    onInput={handleOnInput} // 텍스트를 입력하면 handleOnInput 동작
  />
</form>

문제점

실은 여기에 문제점이 있습니다. 콘솔창에 찍힌 데이터를 확인해주세요.

textContent는 개행 문자가 나타나지 않습니다.
내용을 입력한 곳은 textarea의 값을 가져오는데 개행 문자 \n가 나타납니다.

Elements 탭에서 확인해봅시다아
띄어쓰기를 하면 홀수 번째 띄어쓰기는 &nbsp; 문자로 나타납니다. 짝수 번째 띄어쓰기는 일반 공백으로 나타납니다.
개행은 나타나지 않습니다.. 왜요..

"textContent는 공백 문자(whitespace)를 포함하여 모든 텍스트를 가져옵니다. 따라서, 띄어쓰기, 탭, 개행 문자 등의 공백 문자도 포함됩니다."

라고 하셨잖아요..

innerText를 이용하면 개행을 연속으로 두 번 했을 경우 개행 문자가 한 번 더 추가됩니다.

개행 세 번 했는데 개행 문자는 5개!

마무리

contentEditable를 왜 사용했을까요? 사용해보고 싶어서요.
contentEditable 속성을 알게된 건 꽤 오래 전인데 직접 적용해본 적이 없어서 한 번 사용해보고 싶었습니다. 추후에 해당 페이지에 에디터 기능을 추가하면 좋지 않을까? 미리 조금 적용해볼 수 있는 것을 넣어보자 했지만 생각보다 쉽지 않네요.
에디터 구현하게 되면 그 때 다시 만나자!

form 요소가 아닌 요소에 React-hook-form을 적용할 수 있다는 것 자체로 흥미진진했습니다. 당연하게 form 요소에만 적용할 수 있을거라고 생각했는데, 아니었네요.

그럼 저는 코드 수정하러 가겠습니다. 헿

참고

1개의 댓글

comment-user-thumbnail
2023년 10월 13일

안녕하세요. 리액트 프로젝트에서 contentEditable 속성 사용하실 때 한글 자음, 모음이 분리되어서 입력되는 현상은 없으셨는지 궁금합니다.

답글 달기