8.Forms

(React 공식문서의 main concepts 번역 글입니다.)

함께보면 이해가 쏙쏙 https://youtu.be/9XoL9WDhYeY

React에서,
form element 다른 DOM element와는 조금 다르게 다뤄진다.
form은 내부에 state를 이미 가지고 있기 때문이다.
아래 예시는 name 값을 받는 form이다.

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

위 form은 사용자가 form 내용을 submit 할 때
기본적으로 새로운 페이지로 이동하는 동작을 한다.

만약에 React에서도 똑같이 동작하길 원한다면
그대로 React에서 똑같이 사용하면 된다.(정상 작동 함)

그러나 form을 submit하는 function이 필요할 때가 있다.
또한 user가 form에 입력하는 data에 접근해야할 때도 있다.
그럴때 React에서는 controlled component을 사용한다.

controlled component

form element는 <input>, <textarea>, <select>등이 있다.
form element들은 각자 state를 갖고 있으며
user input값에 따라 그 state 값을 자유롭게 업데이트 할 수 있다.
React에서는 이러한 state값은 component의 property이며
오직 setState()를 사용해서만 업데이트 할 수 있다.

위에 form의 state와 React의 state를 하나로 합치자.
하나로 합친 그 state를 관리하는 component를 만들자.
그 component가 바로 controlled component이다.

controlled component
<input>처럼 state를 가진 tag를 render하며
<input>의 state 또한 관리한다.

그 결과 form element들(ex)<input>)이 입력 받은 data를
관리할 수 있게 된다.

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};  }

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

  handleSubmit = (event) => {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return(
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="Submit" />
      </form>
    )
  }
}

위 NameForm component은 controlled component이다.

먼저, 첫번째 하이라이팅 부분에서 state를 확인할 수 있다.
이 state는 마지막 하이라이팅 부분, <input> value값에 할당된다.
<input>(form element)이 입력받은 value값으로
NameForm component가 자신의 state값을 수정한다.
즉 state를 하나로 합쳤다.

<input>이 동작하는 순서는 아래와 같다.

  1. 사용자가 <input>에 값을 입력한다.
  2. onChange에 할당된 handleChange가 실행된다.
  3. setState에 의해 state값이 바뀐다.
    (사용자에게서 입력받은 값으로 바뀜)
  4. <input>의 value값에 바뀐 state값이 할당된다.
  5. render()가 다시 실행되어 <input>이 rendering된다.

controlled component는 모든 form element state의 변화를
handle하는 handler function을 가진다.
그 결과, 사용자에게 입력을 받거나 입력값을 수정하는 것이 간단해진다.

만약 위 <input>이 입력받은 값을 모두 대문자로 바꾸고 싶을 때는

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});}

위와 같이 value값을 수정해주면 간단하게 바꿀 수 있다.

textarea tag

HTML에서 <textarea>(form element)는 text가 children이다.

<textarea>
  Hello there, this is some text in a text area
</textarea>

React에서 <textarea>는 value를 props로 가진다.
이때 이 <textarea>를 포함하는 form의 형태는
<input>을 포함하는 form의 형태와 매우 비슷하다.

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

첫번째 하이라이팅된 부분에서 state값이 초기화 되었으므로
이 textarea는 render될 때 이미 값(state값)이 쓰여져있다.

select tag

HTML에서 select tag(form element)는 drop-down list를 만든다.

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>  <option value="mango">Mango</option>
</select>

selected 특성 때문에 하이라이팅된 coconut이 초기값이다.

React에서는 초기값을 지정하는 방법으로 selected 특성 대신
select tag의 value라는 특성직접 초기값을 할당한다.
그 결과 controlled component는 state를 업데이트하기가 더 쉽다.

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite flavor:
          <select value={this.state.value} onChange={this.handleChange}>            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

전반적으로 이러한 방식을 사용하면 다음 form element들
<input type="text">, <textarea>, <select>가 모두
비슷하게 동작한다.

즉 value특성의 값을 앞에 form elements에게 위 방식으로 준다면
controlled component가 간단하게 value를 업데이트 할 수 있다.

value특성에게는 array를 값으로 줄 수 있다. 또한 여러개를 선택하는 것도 multiple특성에 true값으로 가능하다.

<select multiple={true} value={['B', 'C']}>

file input tag

HTML에서 <input type="file">를 활용한다면
local 저장소에서 하나 혹은 여러개의 파일을 선택하고
선택한 파일을 서버에 저장하거나
File API를 통해 조작할 수 있다.

<input type="file" />

이 tag의 value값은 읽기만 가능하기 때문에
react에서는 uncontrolled component로 분류된다.

여러개 input tag 들을 동시에 활용하기

여러개 <input>들을 동시에 활용해야 할 때에는
<input>들에게 name property를 준다.
이 name property의 값, 즉 event.target.name값에 따라
handle function이 어떤 동작을 할지 결정하게 한다.

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    this.setState({
      [name]: value    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

동작 원리는 아래와 같다.

  1. <input>의 type은 2가지(checkbox와 value 입력)
  2. handleInputChange는 name이라는 변수를 선언
  3. name에는 event.target.name 이 할당됨
  4. setState()에서 state의 key값은 [name]
  5. <input>의 type이 변하면 <input>의 name값도 변함
  6. name값이 변하면 state의 key값도 변함
  7. setState가 수정하는 state의 값도 변함
  8. setState가 name값에 따라 다른 state 값을 수정함

또한 state값을 수정하기 위해 우리는 state 객체의 key값을
[name]로 표현했다.(computed property name 활용)

this.setState({
  [name]: value
});

위 코드는 아래와 같은 결과를 가져온다.

var partialState = {};
partialState[name] = value;
this.setState(partialState);

setState()가 partialState를 현재 state에 merge하므로
state값이 바뀔 때만 setState()가 실행된다.

Controlled input null value

controlled component에 value props 값을 지정해주면
원하지 않는 경우 <input> value가 바뀌는 걸 막을 수 있다.

만약 value props값을 지정해줬음에도 <input>value가 바뀐다면
실수로 props값을 undefined이나 null로 지정해줬을 수 있다.

아래 예시를 통해 이러한 경우를 확인할 수 있다.

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

controlled components의 대안?

모든 <input>에 대해 state를 관리하고
handle function을 만드는 것이 비효율적으로 느껴질 수 있다.
uncontrolled component를 만날 때 form의 한계를 느낄수도 있다.

Formik(https://jaredpalmer.com/formik/)은 해결책이 될수 있다.


[david hwang]
Written by@[david hwang]
깊게 이해하고 넓게 소통합니다.

GitHubMedium