전에 트위터에서 봤었고 탐토님이랑 리믹스 실습하면서도 봤는데 어제 한 번 다시 html 웹 표준을 얘기하다 리액트에서 form input 값들을 다룰 때 state랑 ref 훅이나 react hook form 라이브러리를 사용하면서 고통받을 필요 없다고 하셔서 마침 게시판을 만드는 김에 배우려고 한다..!
제어 컴포넌트 (Controlled Component)란 <input>
, <textarea>
, <select>
와 같은 HTML 폼 엘리먼트를 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트하는 것으로, 대부분 경우엔 폼을 구현하는데 제어 컴포넌트를 사용하는 것이 좋다고 공식 문서에 나와있다. 리액트에 의해 값이 제어되므로 제어 컴포넌트로 부르는 것
비제어 컴포넌트는 바닐라 자바스크립트처럼 폼을 제출할 때 (onSubmit) form input 내부의 값을 얻어오는 방식으로 ref
를 사용해서 값을 얻는데 값이 실시간을로 동기화되지 않아서 컴포넌트 내부 값의 변화를 즉각적으로 대응할 수 없다.
핵심은 어떤 방식이 좋다 나쁘다가 아니라 언제 제어 컴포넌트와 비제어 컴포넌트를 사용하는 경우인지를 알아야 하는 것 같은데 그럼 formData는 ref를 이용하는 비제어 컴포넌트와 비슷하다고 볼 수 있는 건가??
export default function Editor() {
const catRef = React.useRef() as React.RefObject<HTMLSelectElement>;
const titRef = React.useRef() as React.RefObject<HTMLInputElement>;
...
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const category = catRef.current?.value;
const title = titRef.current?.value;
const instance = editorRef.current?.getInstance();
const data = instance?.getHTML();
console.log(category, title, data);
};
return (
<form onSubmit={onSubmit}>
<div>
<select ref={catRef}>
<option value="">카테고리 선택</option>
<option value="free">자유게시판</option>
<option value="share">정보공유</option>
<option value="activity">활동모집</option>
</select>
<input
ref={titRef}
className="outline-none p-2"
placeholder="글 제목을 입력해 주세요"
/>
</div>
<ToastEditor ... />
</div>
);
}
처음엔 formElemRef를 만들어서 const formData = new FormData(formElemRef);
이렇게 도전했는데 안 돼서 전에 봤던 트위터 글을 찾아서 적용했다.
https://twitter.com/DavidKPiano/status/1569361453928300545
첨부터 이걸 봤어야 했는데..!
방법은 그냥 input에 name 속성을 달아주는 거라 쉬웠는데 뒤에 생각해 보니 카테고리 선택을 안 했거나 제목을 입력하지 않았거나 글자 수 제한 등을 체크하려면 onSubmit 함수 안이나 input에 pattern, maxLength 등을 사용해야 하니 이것도 나름 고충? 이 있겠지만 ref나 state를 여러 개 만들어도 되지 않아서 좋은 것 같다.
export default function Editor() {
...
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// e.target은 빨간줄 떴음..
const formData = new FormData(e.currentTarget);
console.log(formData.get('tit-input'));
const data = Object.fromEntries(formData);
console.log('data', data);
};
return (
<form onSubmit={onSubmit}>
<div>
<select name="cat-input">
<option value="">카테고리 선택</option>
<option value="free">자유게시판</option>
<option value="share">정보공유</option>
<option value="activity">활동모집</option>
</select>
<input
name="tit-input"
placeholder="글 제목을 입력해 주세요"
/>
</div>
<ToastEditor ... />
</div>
);
}
이런 식으로 백엔드에 데이터를 보내면 될 것 같은데, 유효성 검사는 어떻게 하면 좋을지 생각해 봐야겠다.
const handleCreateBoard = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData);
const instance = editorRef.current?.getInstance();
const content = instance?.getHTML();
const newData = {
...data,
imgList,
content,
};
console.log('newData:', newData);
// category: 'free';
// content: '<p>본문!</p>';
// imgList: [];
// title: '제목';
};