> For the complete documentation index, see [llms.txt](https://yamoo9.gitbook.io/learning-react-app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yamoo9.gitbook.io/learning-react-app/learning-history/form-controls.md).

# Form Controls

## HTML 폼 컨트롤 방식 <a href="#html-form-control" id="html-form-control"></a>

HTML 폼 컨트롤 요소(\<input />, \<textarea>, \<select> 등)는 사용자의 입력 내용을 자체적으로 관리합니다. 각 컨트롤의 `value` 값을 JavaScript를 사용해 확인해 입력 내용을 읽거나, 쓸 수 있습니다.

```javascript
const inputEmail = document.querySelector('form input[type="email"]')

// GET: HTML 폼 컨트롤 속성 값 읽기
console.log(inputEmail.value)

// SET: HTML 폼 컨트롤 속성 값 쓰기
inputEmail.value = 'yamoo9@euid.dev'
```

## React 폼 컨트롤 방식 <a href="#react-form-control" id="react-form-control"></a>

클래스 컴포넌트 상태 정보를 `state` 속성으로 관리하며 `setState()` 메서드를 사용해 상태를 업데이트 합니다.

```jsx
class FormControl extends React.Component {

  state = {
    value: '',
  }

  handleChange = (e) => {
    this.setState({ value: e.target.value })
  }

  render() {
    return (
      <input
        value={this.state.value}
        onChange={this.handleChange}
      />
    )
  }
  
}
```

#### 라이브 예제

{% embed url="<https://codesandbox.io/s/sad-dhawan-6ore6?file=/src/App.js>" %}

## Controlled 컴포넌트 <a href="#controlled" id="controlled"></a>

React 컴포넌트는 폼을 통해 입력된 사용자의 값을 제어 할 수 있습니다. React를 통해 값이 관리되는 입력 요소는 컨트롤 컴포넌트(Controlled Component)입니다. 예를 들면 다음의 HTML 폼(\<input /> 포함)을 React에 의해 컨트롤 되는 컴포넌트로 변경할 수 있습니다.

{% tabs %}
{% tab title="HTML Form" %}

```jsx
<form onsubmit="handleSubmit()">
  <label>
    @이메일: 
    <input type="email" name="email" placeholder="yamoo9@euid.dev" />
  </label>
  <button type="submit">전송</button>
</form>
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="React Form" %}

```jsx
class Form extends React.Component {

  state = {
    email: ''
  }
  
  handleInput = (e) => {
    this.setState({ email: e.target.value })
  }
  
  handleSubmit = (e) => {
    e.preventDefault()
    console.log(`입력하신 이메일 ${this.state.email}이 성공적으로 입력되었습니다.`)
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          @이메일:
          <input
            type="email"
            name="email"
            value={this.state.email}
            onChange={e => this.handleInput(e)} 
          />
        </label>
        <button type="submit">전송</button>
      </form>
    )
  }
  
}
```

{% endtab %}
{% endtabs %}

컨트롤 컴포넌트를 사용하면 모든 `state`의 업데이트는 연결 된 이벤트 리스너에 의해 처리됩니다. 컨트롤 컴포넌트를 통해 사용자의 입력 내용을 업데이트 하거나, 유효성 검사 수행이 가능합니다. 예를 들어 입력 내용의 앞/뒤에 공백을 제거하고 싶다면 리스너 코드를 다음과 같이 수정합니다.

```jsx
handleInput = (e) => {
  this.setState({
    email: e.target.value.trim()
  })
}
```

## 멀티 이벤트 핸들링 <a href="#multi-event-handling" id="multi-event-handling"></a>

하나 이상의 \<input />을 하나의 리스너로 컨트롤 할 수 있습니다. 여러 개의 컨트롤을 제어하기 위해서는 `name` 속성을 설정해야 합니다. 사용자가 입력한 등록 정보(email, password)를 처리하는 이벤트 리스너 예를 살펴봅니다.

```jsx
class MultiControlInputs extends React.Component {

  state = {
    // 사용자 등록 정보 객체 (이메일, 패스워드)
    user: {
      email: '',
      password: ''
    }
  }

  // 멀티 이벤트 핸들링 리스너
  handleChange = (e) => {
    // 이벤트 타겟 객체 구조 분해 할당
    const {name, value} = e.target
    // user 상태 업데이트
    this.setState({
      user: {
        ...this.state.user,
        [name]: value 
      }
    })
  }

  render() {
    const { user: { email, password } } = this.state
    
    return (
      <>
        <input
          type="email"
          name="email"
          aria-label="계정 이메일"
          value={email}
          onChange={this.handleChange} />
        <input
          type="password"
          name="password"
          aria-label="계정 패스워드"
          value={password}
          onChange={this.handleChange} />
      </>
    )
  }
  
}
```

#### 라이브 예제

{% embed url="<https://codesandbox.io/s/react-form-keonteurol-02-8ve60?file=/src/components/MultiFormEventHandling.js>" %}

## TextArea 컨트롤 <a href="#textarea-control" id="textarea-control"></a>

HTML \<textarea> 요소는 사용자가 입력한 내용을 자식 텍스트 콘텐츠로 받습니다.

```markup
<textarea>오늘 하루를 정리하는 글을 작성해봅시다.</textarea>
```

하지만 React는 `value` 속성을 대신 사용합니다. 즉, 한 줄 입력을 사용하는 폼 컨트롤과 비슷하게 작성합니다.

```jsx
<textarea
  value={this.state.value}
  onChange={this.handleChange}
/>
```

#### 라이브 예제

{% embed url="<https://codesandbox.io/s/react-form-keonteurol-03-textarea-bcjyr?file=/src/components/MultiFormEventHandling.js>" %}

## Select 컨트롤 <a href="#select-control" id="select-control"></a>

HTML \<select> 요소는 드롭 다운 메뉴를 화면에 렌더링 하고, 초기 값을 `selected` 속성을 사용해 처리합니다.

```markup
<select class="learning-subject">
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>
```

반면, React는 `value` 속성을 사용해 초기 값을 설정합니다.

```jsx
<select
  className="learning-subject"
  value={this.state.value}
  onChange={this.handleChange}
>
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>
```

#### 라이브 예제

{% embed url="<https://codesandbox.io/s/react-form-keonteurol-04-select-keonteurol-c5ft1?file=/src/components/MultiFormEventHandling.js>" %}

## Multiple Select 컨트롤 <a href="#multiple-select-control" id="multiple-select-control"></a>

HTML \<select> 요소를 사용해 하나 이상의 아이템을 선택할 수 있는 멀티플 드롭 다운 메뉴를 사용하려면 `multiple` 속성을 요소에 추가합니다.

```markup
<select class="learning-subject" multiple>
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>
```

반면 React에서는 \<select> 요소에 다음의 2가지 설정이 요구 됩니다.

1. `multiple` 속성 값은 `true` 불리언(Boolean) 데이터 유형으로 설정
2. `value` 속성 값은 배열(Array) 데이터 유형으로 설정

```jsx
<select
  className="learning-subject"
  multiple={true}
  value={this.state.value}
  onChange={this.handleChange}
>
  <option value="">학습 할 주제를 선택하세요.</option>
  <option value="react" selected>리엑트</option>
  <option value="reactRouter">리엑트 라우터</option>
  <option value="redux">리덕스</option>
</select>
```

`value` 초기 값을 배열로 설정한 후, 핸들러 내부에서 사용자의 멀티 선택을 배열 값으로 업데이트 해야 합니다.

```jsx
state = {
  value: []
}

handleChange(e) {
  // select > option 요소 수집 후 배열 데이터로 변경
  const options = Array.from(e.target.children)
  // 사용자가 선택한 option 필터링
  const selectedOptions = options.filter(option => option.selected)
  // 필터링 된 option.value 값을 아이템으로 하는 새로운 배열 반환
  const selectedOptionsValue = selectedOptions.map(option => option.value)
  // 상태 업데이트
  this.setState({value: selectedOptionsValue});
}
```

#### 라이브 예제

{% embed url="<https://codesandbox.io/s/react-form-keonteurol-05-multiple-select-keonteurol-5m9nj?file=/src/components/MultiFormEventHandling.js>" %}

## Uncontrolled 컴포넌트

컨트롤 되지 않은(Uncontrolled) 컴포넌트는 React 외부에서 작동되는 것처럼 처리됩니다. 사용자가 폼 입력 컨트롤 (input, dropdown 등)에 입력하면 업데이트 된 정보가 React에서 별도 처리 과정 없이 요소에 바로 반영됩니다. 즉,  React에 의해 "컨트롤 되지 않음"을 의미 합니다.

{% hint style="info" %}
특별한 경우를 제외하고, 폼 컨트롤은 React에서 제어(Controlled) 하는 것이 권장됩니다.
{% endhint %}

#### File 인풋 요소

HTML \<input type="file" /> 요소는 하나 이상의 파일을 사용자의 로컬 컴퓨터에서 서버로 업로드 할 때 사용합니다.

```markup
<input type="file" />
```

이 컴포넌트는 프로그래밍 방식으로 값을 설정 할 수 없고, 사용자에 의해 값이 설정되는 특별한 경우로 "컨트롤 할 수 없는 컴포넌트" 입니다. 컨트롤 할 수 없는 컴포넌트는 React가 아닌, DOM 자체에서 데이터를 다뤄야 합니다. ([File API ](https://developer.mozilla.org/ko/docs/Web/API/File/Using_files_from_web_applications)사용)

모든 `state` 업데이트를 처리하는 이벤트 핸들러 대신, "컨트롤 할 수 없는 컴포넌트"를 제어하기 위해 직접 DOM을 통해 요소의 사용자 입력 값을 가져와 처리해야 합니다. 문제는 "가상 DOM에서 어떻게 실제 DOM에 접근할 것인가?" 입니다.

## DOM 노드 참조 <a href="#ref-dom-node" id="ref-dom-node"></a>

[`ref`](https://ko.reactjs.org/docs/refs-and-the-dom.html) 속성은 컴포넌트가 렌더링 된 이후의 실제 DOM 노드나 React 요소에 접근해야 할 때 사용하는 방법입니다. 아래 예시는 핸들러에서 파일에 접근하기 위해 DOM 노드의 Ref(참조)를 만드는 방법을 보여주고 있습니다.

#### 참조(Ref.) 생성 <a href="#ref" id="ref"></a>

`ref` 속성을 활용하려면 먼저 [React.createRef() ](https://ko.reactjs.org/docs/react-api.html#reactcreateref)메서드를 사용해 참조 객체를 생성해야 합니다. 생성 된 참조(Ref.)는 `ref` 속성을 통해 DOM 노드 또는 React 요소를 가리키게 됩니다.

```jsx
class FileInput extends Component {
  
  // 참조 객체 생성
  fileInput = React.createRef(null);

  handleSubmit = (event) => {
    event.preventDefault();
    console.log(`선택된 파일: ${this.fileInput.current.files[0].name}`)
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          업로드:
          {/* 참조 할 노드의 ref 속성에 참조 객체 연결 */}
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">전송</button>
      </form>
    );
  }

}
```

#### 참조(Ref.) 접근 <a href="#ref" id="ref"></a>

`ref` 속성이 설정 된 DOM 요소는 `ref.current` 속성을 통해 접근할 수 있습니다.

```jsx
handleSubmit = (event) => {
  event.preventDefault();
  console.log(`선택된 파일: ${this.fileInput.current.files[0].name}`)
}
```

{% hint style="info" %}
React v16.8 부터는 [useRef()](https://ko.reactjs.org/docs/hooks-reference.html#useref) 훅을 사용해 함수형 컴포넌트에서도 **ref** 속성을 사용할 수 있습니다.

```jsx
import React, {useRef} from 'react'


const FunctionalComponent = (props) => {
  const divRef = useRef(null)
  
  return (
    <div ref={divRef}> ... </div>
  )
}
```

{% endhint %}

{% hint style="success" %}

#### 이벤트 대상 활용

사용자에 의해 실행되는 이벤트 리스너를 사용해 핸들링 하는 경우, 반드시 참조(Ref.)를 통해 접근해야 하는 것은 아닙니다. 이벤트 객체를 통해 대상(event.target) 요소에 접근해 조작할 수도 있습니다.
{% endhint %}

#### 라이브 예제

{% embed url="<https://codesandbox.io/s/react-imiji-eobrodeo-ybk2k?file=/src/App.js>" %}
