<textarea> ๋ธŒ๋ผ์šฐ์ € ๋‚ด์žฅ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ์ค„์˜ ํ…์ŠคํŠธ input์„ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<textarea />

๋ ˆํผ๋Ÿฐ์Šค

<textarea>

ํ…์ŠคํŠธ ์˜์—ญ์„ ํ‘œ์‹œํ•˜๋ ค๋ฉด <textarea> ๋ธŒ๋ผ์šฐ์ € ๋‚ด์žฅ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜์„ธ์š”.

<textarea name="postContent" />

์•„๋ž˜ ๋” ๋งŽ์€ ์˜ˆ์‹œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Props

<textarea>๋Š” ์ผ๋ฐ˜์ ์ธ ์—˜๋ฆฌ๋จผํŠธ props๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

ํ…์ŠคํŠธ ์˜์—ญ์„ ์ œ์–ดํ•˜๋ ค๋ฉด value prop์„ ์ „๋‹ฌํ•˜์„ธ์š”.

  • value: ๋ฌธ์ž์—ด ํƒ€์ž…. ํ…์ŠคํŠธ ์˜์—ญ ๋‚ด๋ถ€์˜ ํ…์ŠคํŠธ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

value๋ฅผ ์ „๋‹ฌํ•  ๋• ๋ฐ˜๋“œ์‹œ ํ•ด๋‹น ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” onChange ํ•ธ๋“ค๋Ÿฌ๋„ ํ•จ๊ป˜ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

<textarea>๊ฐ€ ์ œ์–ด๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ defaultValue prop์„ ๋Œ€์‹  ์ „๋‹ฌํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.

  • defaultValue: ๋ฌธ์ž์—ด ํƒ€์ž…. ํ…์ŠคํŠธ ์˜์—ญ ์ดˆ๊นƒ๊ฐ’์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์˜ <textarea> props๋Š” ์ œ์–ด๋˜์ง€ ์•Š๋Š” ํ…์ŠคํŠธ ์˜์—ญ๊ณผ ์ œ์–ด๋˜๋Š” ํ…์ŠคํŠธ ์˜์—ญ ๋ชจ๋‘์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • autoComplete: โ€˜onโ€™ ๋˜๋Š” โ€˜offโ€™. ์ž๋™ ์™„์„ฑ ๋™์ž‘์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • autoFocus: ๋ถˆ๋ฆฌ์–ธ ํƒ€์ž…. true์ผ ๊ฒฝ์šฐ React๋Š” ๋งˆ์šดํŠธ ์‹œ ์—˜๋ฆฌ๋จผํŠธ์— ํฌ์ปค์Šค๋ฅผ ๋งž์ถฅ๋‹ˆ๋‹ค.
  • children: <textarea>๋Š” ์ž์‹์„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ดˆ๊นƒ๊ฐ’์„ ์„ค์ •ํ•˜๋ ค๋ฉด defaultValue๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • cols: ์ˆซ์ž ํƒ€์ž…. ํ‰๊ท  ๋ฌธ์ž ๋„ˆ๋น„์˜ ๊ธฐ๋ณธ ๋„ˆ๋น„๋ฅผ ์ง€์ •ํ•˜์„ธ์š”. ๊ธฐ๋ณธ๊ฐ’์€ 20์ž…๋‹ˆ๋‹ค.
  • disabled: ๋ถˆ๋ฆฌ์–ธ ํƒ€์ž…. true์ผ ๊ฒฝ์šฐ input์€ ์ƒํ˜ธ์ž‘์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์ง€๋ฉฐ ํ๋ฆฟํ•˜๊ฒŒ ๋ณด์ž…๋‹ˆ๋‹ค.
  • form: ๋ฌธ์ž์—ด ํƒ€์ž…. ํ…์ŠคํŠธ ์˜์—ญ input์ด ์†ํ•˜๋Š” <form>์˜ id๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ƒ๋žต ์‹œ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋ถ€๋ชจ ํผ์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.
  • maxLength: ์ˆซ์ž ํƒ€์ž…. ํ…์ŠคํŠธ์˜ ์ตœ๋Œ€ ๊ธธ์ด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • minLength: ์ˆซ์ž ํƒ€์ž…. ํ…์ŠคํŠธ์˜ ์ตœ์†Œ ๊ธธ์ด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • name: ๋ฌธ์ž์—ด ํƒ€์ž…. ํผ๊ณผ ์ œ์ถœ๋˜๋Š” input์˜ ์ด๋ฆ„์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • onChange: ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜. ์ œ์–ด๋˜๋Š” ํ…์ŠคํŠธ ์˜์—ญ ํ•„์ˆ˜ ์š”์†Œ๋กœ ๊ฐ€๋ น ์‚ฌ์šฉ์ž๊ฐ€ ํ‚ค๋ณด๋“œ๋ฅผ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ์‹คํ–‰๋˜๋Š” ๋ฐฉ์‹์œผ๋กœ input ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ์ฆ‰์‹œ ์‹คํ–‰๋˜๋ฉฐ ๋ธŒ๋ผ์šฐ์ € input ์ด๋ฒคํŠธ์ฒ˜๋Ÿผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • onChangeCapture: ์บก์ฒ˜ ๋‹จ๊ณ„์—์„œ ์‹คํ–‰๋˜๋Š” onChange
  • onInput: ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜. ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ์ฆ‰์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€์˜ ์šฉ๋ฒ•์— ๋น„์ถฐ๋ดค์„ ๋•Œ React์—์„œ๋Š” ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” onChange๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ด€์Šต์ ์ž…๋‹ˆ๋‹ค.
  • onInputCapture: ์บก์ฒ˜ ๋‹จ๊ณ„์—์„œ ์‹คํ–‰๋˜๋Š” onInput
  • onInvalid: ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜. ํผ ์ œ์ถœ ์‹œ input์ด ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์‹คํ–‰๋˜๋ฉฐ invalid ๋‚ด์žฅ ์ด๋ฒคํŠธ์™€ ๋‹ฌ๋ฆฌ React onInvalid ์ด๋ฒคํŠธ๋Š” ๋ฒ„๋ธ”๋ง๋ฉ๋‹ˆ๋‹ค.
  • onInvalidCapture: ์บก์ฒ˜ ๋‹จ๊ณ„์—์„œ ์‹คํ–‰๋˜๋Š” onInvalid
  • onSelect: ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜. <textarea> ๋‚ด๋ถ€์˜ ์„ ํƒ ์‚ฌํ•ญ์ด ๋ณ€๊ฒฝ๋œ ํ›„ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. React๋Š” onSelect ์ด๋ฒคํŠธ๋ฅผ ํ™•์žฅํ•˜์—ฌ ์„ ํƒ ์‚ฌํ•ญ์ด ๋น„๊ฑฐ๋‚˜ ํŽธ์ง‘ ์‹œ ์„ ํƒ ์‚ฌํ•ญ์— ์˜ํ–ฅ์„ ๋ผ์น˜๊ฒŒ ๋  ๋•Œ๋„ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • onSelectCapture: ์บก์ฒ˜ ๋‹จ๊ณ„์—์„œ ์‹คํ–‰๋˜๋Š” onSelect
  • placeholder: ๋ฌธ์ž์—ด ํƒ€์ž…. ํ…์ŠคํŠธ ์˜์—ญ ๊ฐ’์ด ๋น„์—ˆ์„ ๋•Œ ํ๋ฆฐ ์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
  • readOnly: ๋ถˆ๋ฆฌ์–ธ ํƒ€์ž…. true์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ ์˜์—ญ์„ ํŽธ์ง‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • required: ๋ถˆ๋ฆฌ์–ธ ํƒ€์ž…. true์ผ ๊ฒฝ์šฐ ํผ์ด ์ œ์ถœํ•  ๊ฐ’์„ ๋ฐ˜๋“œ์‹œ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • rows: ์ˆซ์ž ํƒ€์ž…. ํ‰๊ท  ๋ฌธ์ž ๋†’์ด์˜ ๊ธฐ๋ณธ ๋†’์ด๋ฅผ ์ง€์ •ํ•˜์„ธ์š”. ๊ธฐ๋ณธ๊ฐ’์€ 2์ž…๋‹ˆ๋‹ค.
  • wrap: 'hard', 'soft', 'off' ์ค‘ ํ•˜๋‚˜. ํผ ์ œ์ถœ ์‹œ ํ…์ŠคํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” ๋ฐฉ์‹์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฝ๊ณ 

  • <textarea>something</textarea>์™€ ๊ฐ™์ด ์ž์‹์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ดˆ๊ธฐ ์ฝ˜ํ…์ธ ๋กœ defaultValue๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ํ…์ŠคํŠธ ์˜์—ญ์€ ๋ฌธ์ž์—ด value prop์„ ๋ฐ›์„ ๊ฒฝ์šฐ ์ œ์–ด๋˜๋Š” ๊ฒƒ์œผ๋กœ ์ทจ๊ธ‰๋ฉ๋‹ˆ๋‹ค.
  • ํ…์ŠคํŠธ ์˜์—ญ์€ ์ œ์–ด๋˜๋ฉด์„œ ๋™์‹œ์— ๋น„์ œ์–ด๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • ํ…์ŠคํŠธ ์˜์—ญ์€ ์ƒ๋ช…์ฃผ๊ธฐ ๋™์•ˆ ์ œ์–ด ๋˜๋Š” ๋น„์ œ์–ด ์ƒํƒœ๋ฅผ ์˜ค๊ฐˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • ์ œ์–ด๋˜๋Š” ํ…์ŠคํŠธ ์˜์—ญ์—” ๋ชจ๋‘ ๋ฐฑ์—… ๊ฐ’์„ ๋™๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋Š” onChange ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

ํ…์ŠคํŠธ ์˜์—ญ ํ‘œ์‹œํ•˜๊ธฐ

ํ…์ŠคํŠธ ์˜์—ญ์„ ํ‘œ์‹œํ•˜๋ ค๋ฉด <textarea>๋ฅผ ๋ Œ๋”๋งํ•˜์„ธ์š”. rows์™€ cols ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋กœ ํ…์ŠคํŠธ ์˜์—ญ์˜ ๊ธฐ๋ณธ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ ์˜์—ญ์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํฌ๊ธฐ ์กฐ์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด CSS์—์„œ resize: none์„ ์ง€์ •ํ•˜์„ธ์š”.

export default function NewPost() {
  return (
    <label>
      Write your post:
      <textarea name="postContent" rows={4} cols={40} />
    </label>
  );
}


ํ…์ŠคํŠธ ์˜์—ญ ๋ผ๋ฒจ ์ œ๊ณตํ•˜๊ธฐ

์ผ๋ฐ˜์ ์œผ๋กœ ๋ชจ๋“  <textarea>๋Š” <label> ํƒœ๊ทธ ์•ˆ์— ๋‘๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ด๋‹น ๋ผ๋ฒจ์ด ํ•ด๋‹น ํ…์ŠคํŠธ ์˜์—ญ๊ณผ ์—ฐ๊ด€๋จ์„ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋ผ๋ฒจ์„ ํด๋ฆญํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ํ…์ŠคํŠธ ์˜์—ญ์— ํฌ์ปค์Šค๋ฅผ ๋งž์ถฅ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ ์˜์—ญ์— ํฌ์ปค์Šค๋ฅผ ๋งž์ถœ ๋•Œ ๋ผ๋ฒจ ์บก์…˜์„ ์ฝ๊ฒŒ ๋˜๋ฏ€๋กœ ์ด ๋ฐฉ์‹์€ ์ ‘๊ทผ์„ฑ์„ ์œ„ํ•ด์„œ๋„ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.

<label> ์•ˆ์— <textarea>๋ฅผ ๊ฐ์Œ€ ์ˆ˜ ์—†๋‹ค๋ฉด <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}
      />
    </>
  );
}


ํ…์ŠคํŠธ ์˜์—ญ ์ดˆ๊นƒ๊ฐ’ ์ œ๊ณตํ•˜๊ธฐ

ํ…์ŠคํŠธ ์˜์—ญ ์ดˆ๊นƒ๊ฐ’์€ ์„ ํƒ์ ์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. defaultValue ๋ฌธ์ž์—ด๋กœ ์ „๋‹ฌํ•˜์„ธ์š”.

export default function EditPost() {
  return (
    <label>
      Edit your post:
      <textarea
        name="postContent"
        defaultValue="I really enjoyed biking yesterday!"
        rows={4}
        cols={40}
      />
    </label>
  );
}

Pitfall

HTML๊ณผ ๋‹ฌ๋ฆฌ <textarea>Some content</textarea>์™€ ๊ฐ™์€ ์ดˆ๊ธฐ ํ…์ŠคํŠธ ์ „๋‹ฌ์€ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


ํผ ์ œ์ถœ ์‹œ ํ…์ŠคํŠธ ์˜์—ญ ๊ฐ’ ์ฝ๊ธฐ

textarea์™€ <button type="submit"> ๋ฐ”๊นฅ์„ <form>์œผ๋กœ ๊ฐ์‹ธ๋ฉด ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ <form onSubmit> ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋Š” ํ˜„์žฌ URL์— ํผ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•œ ํ›„ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋ฉฐ ์ด๋Ÿฌํ•œ ๋™์ž‘์€ e.preventDefault()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํผ ๋ฐ์ดํ„ฐ๋Š” new FormData(e.target)๋กœ ์ฝ์œผ์„ธ์š”.

export default function EditPost() {
  function handleSubmit(e) {
    // Prevent the browser from reloading the page
    e.preventDefault();

    // Read the form data
    const form = e.target;
    const formData = new FormData(form);

    // You can pass formData as a fetch body directly:
    fetch('/some-api', { method: form.method, body: formData });

    // Or you can work with it as a plain object:
    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>
  );
}

Note

<textarea name="postContent" /> ์˜ˆ์‹œ์™€ ๊ฐ™์ด <textarea>์— name์„ ๋ถ€์—ฌํ•˜์„ธ์š”. ํ•ด๋‹น name์€ { postContent: "Your post" } ์˜ˆ์‹œ์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ์˜ key๋กœ ์“ฐ์ž…๋‹ˆ๋‹ค.

Pitfall

๊ธฐ๋ณธ์ ์œผ๋กœ <form> ๋‚ด๋ถ€์˜ ์–ด๋Š <button>์ด๋“  ํผ์„ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋œป๋ฐ–์ธ๊ฐ€์š”? ์ปค์Šคํ…€ Button React ์ปดํฌ๋„ŒํŠธ์˜ ๊ฒฝ์šฐ <button> ๋Œ€์‹  <button type="button"> ๋ฐ˜ํ™˜์„ ๊ณ ๋ คํ•˜์„ธ์š”. ๋ช…์‹œ์„ฑ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์œ„ํ•ด ํผ ์ œ์ถœ์šฉ ๋ฒ„ํŠผ์œผ๋กœ๋Š” <button type="submit">์„ ์‚ฌ์šฉํ•˜์„ธ์š”.


state ๋ณ€์ˆ˜๋กœ ํ…์ŠคํŠธ ์˜์—ญ ์ œ์–ดํ•˜๊ธฐ

<textarea />์™€ ๊ฐ™์€ ํ…์ŠคํŠธ ์˜์—ญ์€ ์ œ์–ด๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. <textarea defaultValue="Initial text" />์™€ ๊ฐ™์€ ์ดˆ๊นƒ๊ฐ’์„ ์ „๋‹ฌํ•œ๋Œ€๋„ JSX๋Š” ๋‹น์žฅ์˜ ๊ฐ’์ด ์•„๋‹Œ ์ดˆ๊นƒ๊ฐ’๋งŒ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

์ œ์–ด๋˜๋Š” ํ…์ŠคํŠธ ์˜์—ญ์„ ๋ Œ๋”๋งํ•˜๋ ค๋ฉด value prop์„ ์ „๋‹ฌํ•˜์„ธ์š”. React๋Š” ์ „๋‹ฌํ•œ value๋ฅผ ํ•ญ์ƒ ๊ฐ–๋„๋ก ํ…์ŠคํŠธ ์˜์—ญ์— ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํ…์ŠคํŠธ ์˜์—ญ์€ state ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜์—ฌ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

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} />
    </>
  );
}

Pitfall

ํ…์ŠคํŠธ ์˜์—ญ์— onChange ์—†์ด value๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ํ•ด๋‹น ํ…์ŠคํŠธ ์˜์—ญ์— ํƒ€์ดํ•‘์„ ํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. value๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ํ…์ŠคํŠธ ์˜์—ญ์„ ์ œ์–ดํ•˜๋ฉด ํ•ญ์ƒ ํ•ด๋‹น value๋ฅผ ๊ฐ€์ง€๋„๋ก ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ state ๋ณ€์ˆ˜๋ฅผ value๋กœ ์ „๋‹ฌํ•ด๋„ onChange ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด์—์„œ ํ•ด๋‹น state ๋ณ€์ˆ˜๋ฅผ ๋™๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์œผ๋ฉด React๋Š” ํ‚ค๋ณด๋“œ๋ฅผ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ํ…์ŠคํŠธ ์˜์—ญ์„ ์ฒ˜์Œ ์ง€์ •ํ•œ value๋กœ ๋˜๋Œ๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.


๋ฌธ์ œ ํ•ด๊ฒฐ

ํ…์ŠคํŠธ ์˜์—ญ์— ํƒ€์ดํ•‘์„ ํ•ด๋„ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

onChange ์—†์ด value๋งŒ ์ „๋‹ฌํ•˜์—ฌ ํ…์ŠคํŠธ ์˜์—ญ์„ ๋ Œ๋”๋งํ•˜๋ฉด ์ฝ˜์†”์— ์—๋Ÿฌ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

// ๐Ÿ”ด ๋ฒ„๊ทธ: ์ œ์–ด๋˜๋Š” ํ…์ŠคํŠธ ์˜์—ญ์— onChange ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
<textarea value={something} />
Console
ํผ ํ•„๋“œ์— onChange ํ•ธ๋“ค๋Ÿฌ ์—†์ด value prop๋งŒ ์ „๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ๊ธฐ ์ „์šฉ ํ•„๋“œ๋ฅผ ๋ Œ๋”๋งํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํ•„๋“œ๊ฐ€ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ defaultValue๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ onChange ๋˜๋Š” readOnly๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ์ œ์•ˆํ•˜๋“ฏ ์ดˆ๊นƒ๊ฐ’๋งŒ ์ง€์ •ํ•˜๋ ค๋ฉด defaultVallue๋ฅผ ๋Œ€์‹  ์ „๋‹ฌํ•˜์„ธ์š”.

// โœ… ์ข‹์€ ์˜ˆ: ์ œ์–ด๋˜์ง€ ์•Š๋Š” ํ…์ŠคํŠธ ์˜์—ญ์— ์ดˆ๊นƒ๊ฐ’ ์ „๋‹ฌ
<textarea defaultValue={something} />

ํ…์ŠคํŠธ ์˜์—ญ์„ state ๋ณ€์ˆ˜๋กœ ์ œ์–ดํ•˜๋ ค๋ฉด onChange ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ง€์ •ํ•˜์„ธ์š”.

// โœ… ์ข‹์€ ์˜ˆ: ์ œ์–ด๋˜๋Š” ํ…์ŠคํŠธ ์˜์—ญ์— onChange ์ „๋‹ฌ
<textarea value={something} onChange={e => setSomething(e.target.value)} />

๊ฐ’์ด ๋‚ด๋ถ€์ ์œผ๋กœ ์ฝ๊ธฐ ์ „์šฉ์ด๋ผ๋ฉด ์—๋Ÿฌ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด readOnly prop์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

// โœ… ์ข‹์€ ์˜ˆ: ์ œ์–ด๋˜๋Š” ์ฝ๊ธฐ ์ „์šฉ ํ…์ŠคํŠธ ์˜์—ญ์— onChange ์ƒ๋žต
<textarea value={something} readOnly={true} />

ํ‚ค๋ณด๋“œ๋ฅผ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ํ…์ŠคํŠธ ์ปค์„œ๊ฐ€ ํ…์ŠคํŠธ ์˜์—ญ์˜ ์ฒ˜์Œ์œผ๋กœ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค

ํ…์ŠคํŠธ ์˜์—ญ์„ ์ œ์–ดํ•  ๊ฒฝ์šฐ onChange ์•ˆ์—์„œ state ๋ณ€์ˆ˜๋ฅผ DOM์—์„œ ๋ฐ›์•„์˜จ ํ…์ŠคํŠธ ์˜์—ญ ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

state ๋ณ€์ˆ˜๋Š” e.target.value ์™ธ์˜ ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

function handleChange(e) {
// ๐Ÿ”ด ๋ฒ„๊ทธ: input์„ e.target.value ์™ธ์˜ ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
setFirstName(e.target.value.toUpperCase());
}

๋น„๋™๊ธฐ๋กœ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜๋„ ์—†์Šต๋‹ˆ๋‹ค.

function handleChange(e) {
// ๐Ÿ”ด ๋ฒ„๊ทธ: input์„ ๋น„๋™๊ธฐ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
setTimeout(() => {
setFirstName(e.target.value);
}, 100);
}

์ฝ”๋“œ๋ฅผ ๊ณ ์น˜๋ ค๋ฉด e.target.value๋กœ ๋™๊ธฐ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”.

function handleChange(e) {
// โœ… ์ œ์–ด๋˜๋Š” input์„ e.target.value๋กœ ๋™๊ธฐ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
setFirstName(e.target.value);
}

์ด ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ํ‚ค๋ณด๋“œ๋ฅผ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ํ…์ŠคํŠธ ์˜์—ญ์ด ์ œ๊ฑฐ ํ›„ ๋‹ค์‹œ ์ถ”๊ฐ€๋˜๊ณ  ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ˆ˜๋กœ ๋ฆฌ๋ Œ๋”๋ง๋งˆ๋‹ค state๋ฅผ ์žฌ์„ค์ •ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ๋Š” ํ˜„์ƒ์ž…๋‹ˆ๋‹ค. ๊ฐ€๋ น ํ…์ŠคํŠธ ์˜์—ญ์ด๋‚˜ ํ…์ŠคํŠธ ์˜์—ญ์˜ ๋ถ€๋ชจ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋งค๋ฒˆ ๋‹ค๋ฅธ key ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ๋ฐ›๊ฑฐ๋‚˜ ์ปดํฌ๋„ŒํŠธ ์ •์˜๋ฅผ ์ค‘์ฒฉ์‹œํ‚ค๋Š” ๊ฒฝ์šฐ(์ด๋Š” React์—์„œ ํ—ˆ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ ๋ Œ๋”๋ง๋งˆ๋‹ค โ€˜๋‚ด๋ถ€โ€™ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋งˆ์šดํŠธ๋˜๋Š” ์›์ธ์ด ๋ฉ๋‹ˆ๋‹ค)์— ํ•ด๋‹น ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค. โ€œA component is changing an uncontrolled input to be controlled(์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ œ์–ด๋˜์ง€ ์•Š๋Š” input์„ ์ œ์–ด ์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค)โ€

์ปดํฌ๋„ŒํŠธ์— value๋ฅผ ์ œ๊ณตํ•  ๊ฒฝ์šฐ ๋ฐ˜๋“œ์‹œ ์ƒ๋ช…์ฃผ๊ธฐ ๋‚ด๋‚ด ๋ฌธ์ž์—ด ํƒ€์ž…์œผ๋กœ ๋‚จ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋น„์ œ์–ดํ•  ๊ฒƒ์ธ์ง€ ์ œ์–ดํ•  ๊ฒƒ์ธ์ง€ ์˜๋„๋ฅผ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ฒ˜์Œ์—” value={undefined}๋ฅผ ์ „๋‹ฌํ–ˆ๋‹ค๊ฐ€ ๋‚˜์ค‘์— ๋‹ค์‹œ value="some string"์„ ์ „๋‹ฌํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ œ์–ด๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ํ•ญ์ƒ null์ด๋‚˜ undefined๊ฐ€ ์•„๋‹Œ ๋ฌธ์ž์—ด value๋ฅผ ๋ฐ›์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

value๊ฐ€ API๋‚˜ state ๋ณ€์ˆ˜์—์„œ ์˜จ๋‹ค๋ฉด null์ด๋‚˜ undefined๋กœ ์ดˆ๊ธฐํ™”๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿด ๊ฒฝ์šฐ ๋นˆ ๋ฌธ์ž์—ด('')์„ ์ดˆ๊นƒ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ value๊ฐ€ ๋ฌธ์ž์—ด์ž„์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด value={someValue ?? ''}๋ฅผ ์ „๋‹ฌํ•˜์„ธ์š”.