<textarea>브라우저 내장 <textarea> 컴포넌트를 사용하면 여러 줄의 텍스트 입력을 렌더링할 수 있어요.
<textarea />
<textarea>는 긴 텍스트를 입력받을 때 사용하는 폼 요소예요. 댓글, 게시글, 메모 등 여러 줄에 걸친 텍스트가 필요할 때 유용해요!
<textarea>text area를 표시하려면, 브라우저 내장 <textarea> 컴포넌트를 렌더링하세요.
<textarea name="postContent" />
<textarea>는 모든 공통 요소 props를 지원해요.
value prop을 전달해서 text area를 제어할 수 있어요:
value: 문자열이에요. text area 내부의 텍스트를 제어해요.value를 전달하면, 전달된 값을 업데이트하는 onChange 핸들러도 반드시 전달해야 해요.
<textarea>가 비제어라면, 대신 defaultValue prop을 전달할 수 있어요:
defaultValue: 문자열이에요. text area의 초기 값을 지정해요.다음 <textarea> props는 비제어 text area와 제어 text area 모두에 관련이 있어요:
autoComplete: 'on' 또는 'off'예요. 자동완성 동작을 지정해요.autoFocus: boolean이에요. true면, React가 마운트 시 요소에 포커스할 거예요.children: <textarea>는 children을 받지 않아요. 초기 값을 설정하려면, defaultValue를 사용하세요.cols: 숫자예요. 평균 문자 너비로 기본 너비를 지정해요. 기본값은 20이에요.disabled: boolean이에요. true면, input은 인터랙티브하지 않고 흐리게 표시될 거예요.form: 문자열이에요. 이 input이 속한 <form>의 id를 지정해요. 생략되면, 가장 가까운 부모 form이에요.maxLength: 숫자예요. 텍스트의 최대 길이를 지정해요.minLength: 숫자예요. 텍스트의 최소 길이를 지정해요.name: 문자열이에요. 폼과 함께 제출되는 이 input의 이름을 지정해요.onChange: 이벤트 핸들러 함수예요. 제어 text area에 필수예요. 사용자가 input의 값을 변경하면 즉시 실행돼요(예: 키 입력마다 실행됨). 브라우저 input 이벤트처럼 동작해요.onChangeCapture: 캡처 단계에서 실행되는 onChange 버전이에요.onInput: 이벤트 핸들러 함수예요. 사용자가 값을 변경하면 즉시 실행돼요. 역사적인 이유로, React에서는 비슷하게 동작하는 onChange를 사용하는 것이 관용적이에요.onInputCapture: 캡처 단계에서 실행되는 onInput 버전이에요.onInvalid: 이벤트 핸들러 함수예요. 폼 제출 시 input이 유효성 검사에 실패하면 실행돼요. 내장 invalid 이벤트와 달리, React onInvalid 이벤트는 버블링돼요.onInvalidCapture: 캡처 단계에서 실행되는 onInvalid 버전이에요.onSelect: 이벤트 핸들러 함수예요. <textarea> 내부의 선택 영역이 변경된 후 실행돼요. React는 onSelect 이벤트를 확장해서 빈 선택이나 편집(선택 영역에 영향을 줄 수 있음)에 대해서도 실행돼요.onSelectCapture: 캡처 단계에서 실행되는 onSelect 버전이에요.placeholder: 문자열이에요. text area 값이 비어있을 때 흐린 색으로 표시돼요.readOnly: boolean이에요. true면, 사용자가 text area를 편집할 수 없어요.required: boolean이에요. true면, 폼을 제출하려면 값이 제공되어야 해요.rows: 숫자예요. 평균 문자 높이로 기본 높이를 지정해요. 기본값은 2예요.wrap: 'hard', 'soft', 또는 'off' 중 하나예요. 폼을 제출할 때 텍스트가 어떻게 줄바꿈되어야 하는지 지정해요.<textarea>something</textarea>처럼 children을 전달하는 것은 허용되지 않아요. 초기 콘텐츠는 defaultValue를 사용하세요.value prop을 받으면, 제어되는 것으로 취급될 거예요.onChange 이벤트 핸들러가 필요해요.text area를 표시하려면 <textarea>를 렌더링하세요. rows와 cols 속성으로 기본 크기를 지정할 수 있지만, 기본적으로 사용자가 크기를 조절할 수 있어요. 크기 조절을 비활성화하려면, CSS에서 resize: none을 지정할 수 있어요.
export default function NewPost() {
return (
<label>
Write your post:
<textarea name="postContent" rows={4} cols={40} />
</label>
);
}
input { margin-left: 5px; }
textarea { margin-top: 10px; }
label { margin: 10px; }
label, textarea { display: block; }
일반적으로 모든 <textarea>를 <label> 태그 안에 배치할 거예요. 이렇게 하면 브라우저에게 이 라벨이 해당 text area와 연결되어 있다고 알려줘요. 사용자가 라벨을 클릭하면, 브라우저가 text area에 포커스할 거예요. 접근성에도 필수적이에요: 스크린 리더가 사용자가 text area에 포커스할 때 라벨 캡션을 알려줄 거예요.
<textarea>를 <label> 안에 중첩할 수 없다면, <textarea id>와 <label htmlFor>에 같은 ID를 전달해서 연결하세요. 한 컴포넌트의 인스턴스 간 충돌을 피하려면, useId로 그런 ID를 생성하세요.
import { useId } from 'react';
export default function Form() {
const postTextAreaId = useId();
return (
<>
<label htmlFor={postTextAreaId}>
Write your post:
</label>
<textarea
id={postTextAreaId}
name="postContent"
rows={4}
cols={40}
/>
</>
);
}
input { margin: 5px; }
text area의 초기 값을 선택적으로 지정할 수 있어요. defaultValue 문자열로 전달하세요.
export default function EditPost() {
return (
<label>
Edit your post:
<textarea
name="postContent"
defaultValue="I really enjoyed biking yesterday!"
rows={4}
cols={40}
/>
</label>
);
}
input { margin-left: 5px; }
textarea { margin-top: 10px; }
label { margin: 10px; }
label, textarea { display: block; }
⚠️ 주의
HTML과 달리,
<textarea>Some content</textarea>처럼 초기 텍스트를 전달하는 것은 지원되지 않아요.
textarea 주위에 <form>을 추가하고 내부에 <button type="submit">을 넣으세요. <form onSubmit> 이벤트 핸들러를 호출할 거예요. 기본적으로 브라우저는 폼 데이터를 현재 URL로 전송하고 페이지를 새로고침해요. e.preventDefault()를 호출해서 그 동작을 재정의할 수 있어요. new FormData(e.target)로 폼 데이터를 읽으세요.
export default function EditPost() {
function handleSubmit(e) {
// 브라우저가 페이지를 새로고침하는 것을 막아요
e.preventDefault();
// 폼 데이터를 읽어요
const form = e.target;
const formData = new FormData(form);
// formData를 fetch body로 직접 전달할 수 있어요:
fetch('/some-api', { method: form.method, body: formData });
// 또는 일반 객체로 작업할 수 있어요:
const formJson = Object.fromEntries(formData.entries());
console.log(formJson);
}
return (
<form method="post" onSubmit={handleSubmit}>
<label>
Post title: <input name="postTitle" defaultValue="Biking" />
</label>
<label>
Edit your post:
<textarea
name="postContent"
defaultValue="I really enjoyed biking yesterday!"
rows={4}
cols={40}
/>
</label>
<hr />
<button type="reset">Reset edits</button>
<button type="submit">Save post</button>
</form>
);
}
label { display: block; }
input { margin: 5px; }
참고
<textarea>에name을 지정하세요. 예를 들어<textarea name="postContent" />. 지정한name은 폼 데이터에서 키로 사용될 거예요. 예를 들어{ postContent: "Your post" }.
⚠️ 주의
기본적으로,
<form>내부의 모든<button>은 폼을 제출할 거예요. 이건 놀랄 수 있어요! 자체 커스텀ButtonReact 컴포넌트가 있다면,<button>대신<button type="button">을 반환하는 것을 고려하세요. 그런 다음, 명시적으로 하기 위해, 폼을 제출해야 하는 버튼에는<button type="submit">을 사용하세요.
<textarea /> 같은 text area는 비제어예요. 초기 값을 전달하더라도 <textarea defaultValue="Initial text" />처럼, JSX는 초기 값만 지정하고 지금 값은 지정하지 않아요.
제어 text area를 렌더링하려면, value prop을 전달하세요. React는 text area가 항상 전달한 value를 갖도록 강제할 거예요. 일반적으로 state 변수를 선언해서 text area를 제어해요:
function NewPost() {
const [postContent, setPostContent] = useState(''); // state 변수를 선언해요...
// ...
return (
<textarea
value={postContent} // ...input의 값이 state 변수와 일치하도록 강제해요...
onChange={e => setPostContent(e.target.value)} // ... 그리고 모든 편집마다 state 변수를 업데이트해요!
/>
);
}
이건 모든 키 입력에 반응해서 UI의 일부를 다시 렌더링하고 싶을 때 유용해요.
import { useState } from 'react';
import MarkdownPreview from './MarkdownPreview.js';
export default function MarkdownEditor() {
const [postContent, setPostContent] = useState('_Hello,_ **Markdown**!');
return (
<>
<label>
Enter some markdown:
<textarea
value={postContent}
onChange={e => setPostContent(e.target.value)}
/>
</label>
<hr />
<MarkdownPreview markdown={postContent} />
</>
);
}
// src/MarkdownPreview.js
import { Remarkable } from 'remarkable';
const md = new Remarkable();
export default function MarkdownPreview({ markdown }) {
const renderedHTML = md.render(markdown);
return <div dangerouslySetInnerHTML={{__html: renderedHTML}} />;
}
// package.json
{
"dependencies": {
"react": "latest",
"react-dom": "latest",
"react-scripts": "latest",
"remarkable": "2.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
textarea { display: block; margin-top: 5px; margin-bottom: 10px; }
위 예시는 마크다운 에디터예요. textarea에 마크다운 문법으로 텍스트를 입력하면, 아래에 실시간으로 렌더링된 미리보기가 나타나요!
⚠️ 주의
onChange없이value를 전달하면, text area에 타이핑하는 것이 불가능할 거예요.value를 전달해서 text area를 제어하면, 전달한 값을 항상 갖도록 강제해요. 그래서 state 변수를value로 전달하지만onChange이벤트 핸들러 동안 그 state 변수를 동기적으로 업데이트하는 것을 잊으면, React는 모든 키 입력 후 text area를 지정한value로 되돌릴 거예요.
value는 있지만 onChange가 없는 text area를 렌더링하면, 콘솔에 에러가 표시될 거예요:
// 🔴 버그: onChange 핸들러가 없는 제어 text area
<textarea value={something} />
⛔
onChange핸들러 없이 폼 필드에valueprop을 제공했어요. 이렇게 하면 읽기 전용 필드가 렌더링될 거예요. 필드가 변경 가능해야 한다면defaultValue를 사용하세요. 그렇지 않으면,onChange나readOnly중 하나를 설정하세요.
에러 메시지가 제안하듯이, 초기 값만 지정하고 싶었다면, 대신 defaultValue를 전달하세요:
// ✅ 좋아요: 초기 값이 있는 비제어 text area
<textarea defaultValue={something} />
state 변수로 이 text area를 제어하고 싶다면, onChange 핸들러를 지정하세요:
// ✅ 좋아요: onChange가 있는 제어 text area
<textarea value={something} onChange={e => setSomething(e.target.value)} />
값이 의도적으로 읽기 전용이라면, readOnly prop을 추가해서 에러를 제거하세요:
// ✅ 좋아요: onChange 없는 읽기 전용 제어 text area
<textarea value={something} readOnly={true} />
text area를 제어한다면, onChange 동안 state 변수를 DOM의 text area 값으로 업데이트해야 해요.
e.target.value 이외의 것으로 업데이트할 수 없어요:
function handleChange(e) {
// 🔴 버그: e.target.value 이외의 것으로 input 업데이트
setFirstName(e.target.value.toUpperCase());
}
비동기적으로도 업데이트할 수 없어요:
function handleChange(e) {
// 🔴 버그: 비동기적으로 input 업데이트
setTimeout(() => {
setFirstName(e.target.value);
}, 100);
}
코드를 수정하려면, e.target.value로 동기적으로 업데이트하세요:
function handleChange(e) {
// ✅ e.target.value로 제어 input을 동기적으로 업데이트
setFirstName(e.target.value);
}
이렇게 해도 문제가 해결되지 않으면, 모든 키 입력마다 text area가 DOM에서 제거되고 다시 추가되는 것일 수 있어요. 이건 모든 리렌더링마다 실수로 state를 리셋하는 경우 발생할 수 있어요. 예를 들어 text area나 부모 중 하나가 항상 다른 key 속성을 받거나, 컴포넌트 정의를 중첩하는 경우(React에서 허용되지 않고 "내부" 컴포넌트가 매 렌더링마다 다시 마운트됨) 그럴 수 있어요.
컴포넌트에 value를 제공하면, 생명주기 동안 문자열로 유지되어야 해요.
먼저 value={undefined}를 전달하고 나중에 value="some string"을 전달할 수 없어요. React는 컴포넌트를 비제어로 할지 제어로 할지 알 수 없어요. 제어 컴포넌트는 항상 문자열 value를 받아야 해요. null이나 undefined가 아니에요.
value가 API나 state 변수에서 오는 경우, null이나 undefined로 초기화될 수 있어요. 그런 경우, 처음에 빈 문자열('')로 설정하거나, value={someValue ?? ''}를 전달해서 value가 문자열이 되도록 하세요.