React (6) update & form

2020-06-27

Form

form은 react의 메인 컨셉 중 하나. 이해도가 필요하다.
react 공식 문서에서 Main concepts → Forms로 가면
form을 다루는 방법을 자세히 설명해줌.

Update

Update 구현

우선, update를 구현하기 위해서는
update form에 기존에 존재하던 title과 description이 표시되어야 한다.
그래야 그 내용을 고치던 삭제하던 할 수 있으니까.

그래서, 상위 component에서 props를 통해 해당 데이터를 내려 보내자.

getReadContent() {
  {/* 지금 화면에 표시되고 있는 컨텐츠의 id를 찾음 */}
  var i = 0;
  while(i < this.state.contents.length) {
    var data = this.state.contents[i];
    if(data.id === this.state.selected_content_id) {
      return data;
    }
    i = i + 1;
  }
}
...
else if (this.state.mode === "update") {
  _content = this.getReadContent();
  _article = <UpdateContent data={_content} onSubmitting={function(_title, _desc) {
    var _contents = this.state.contents.concat(
      {id:this.state.selected_content_id, 
       title: _title, 
       desc: _desc});
    this.setState({contents : _contents});
  }.bind(this)}></UpdateContent>;
}

목차에서 선택된 내용을 data props를 통해 UpdateContent로 내려보냈다.
이것을 UpdateContent의 form 내부의 input에다가 기본값으로 띄워주기 위해서는 value 값을 설정해주면 된다.

class UpdateContent extends Component {
  render() {
    return(
           ...
          <p><input 
            type="text" 
            name="title" 
            placeholder="title"
            value={this.props.data.title}></input>
          </p>
          ...
    );
  }
}

근데, 이렇게 하면 에러가 발생함.
props를 직접 value에다가 넣어버린 상태로, onChange 핸들러를 사용하지 않으면 걔는 read-only가 된다.
라는 에러이다.

그러고 update form에서 title을 수정하려고 보면, 수정이 안되는 것을 알 수 있다.
말 그대로, 하위 component에서는 props가 read-only이기 때문.

그래서 그 read-only 데이터를 value값으로 지정하고 그걸 고치려 들면,
React가 중간에 개입해서 아무것도 못하게 막아버린다.
어떻게 해야 할까?

state는 가변적인 데이터니까,
하위 component 내부에서 그 props 데이터를 state화 시키면 된다.

class UpdateContent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title : this.props.data.title {/* state에다가 박아줬다. */}
    }
  }
  render() {
    console.log(this.props.data);
    return(
          ...
          <p><input 
            type="text" 
            name="title" 
            placeholder="title"
            value={this.state.title}></input> {/* 얘도 state에서 꺼내 쓰도록 바꿈. */}
          </p>
          ...
    );
  }
}

근데, 이렇게 해도 여전히 수정은 안됨.

우리가 input의 value를 수정한다고 해서, state의 데이터가 바뀐다는 근거가 없다.
만약 그 input의 value가 그냥 바뀌어 버리면,
그 바뀐 value값은 state와 관련이 없는 값이 되어버리므로
해당 input과 state 사이의 연결이 끊어지는 것과 마찬가지.

우리는 input의 value를 수정했을 때, state의 데이터도 수정되기를 원한다.
이를 위해서는 onChange 이벤트를 이용해서 input에 변화가 생길 때마다
setState를 통해 state의 값을 바꿔주는 과정이 필요하다.

<p><input 
  type="text" 
  name="title" 
  placeholder="title"
  value={this.state.title}
  onChange={function(e) {
    this.setState({title:e.target.value});
   }.bind(this)}></input>
</p>

<p><textarea 
  name="desc" 
  placeholder="description"
  value={this.state.desc}
  onChange={function(e) {
    this.setState({desc : e.target.value.desc});
  }.bind(this)}></textarea>
</p>

이렇게 하면, 우리가 input에 한 글자를 입력할 때마다
state의 title도 그에 따라 동적으로 변하게 된다.
(textarea도 동일하게 처리해줌)

id를 위한 hidden form

update를 하려면, 여러 개의 content 데이터들 중
어떤 데이터를 update할 것인가에 관한 식별자가 필요하다.
이를 위해서, 드디어 content 배열에 넣어놨던 id 데이터를 이용할 차례.

render() {
  console.log(this.props.data);
  return(
    <article>
      <h2>Update</h2>
      <form action="/update_process" method="post" onSubmit={function(e) {
        e.preventDefault();
        this.props.onSubmitting(
          this.state.id,
          e.target.title.value, 
          e.target.desc.value
        );
      }.bind(this)}>
        <input type="hidden" name="id" value={this.state.id}></input> // 요거.
        <p><input 
          type="text" 
          name="title" 
          placeholder="title"
          value={this.state.title}
          onChange={function(e) {
            this.setState({title:e.target.value});
          }.bind(this)}></input>
        </p>
        <p><textarea 
          name="desc" 
          placeholder="description"
          value={this.state.desc}
          onChange={function(e) {
            this.setState({desc : e.target.value});
          }.bind(this)}></textarea></p>
        <p><input type="submit"></input></p>
      </form>
    </article>
  );
}

// 상위 component인 App에서는 요렇게 받고 있다.
else if (this.state.mode === "update") {
  _content = this.getReadContent();
  _article = <UpdateContent data={_content} onSubmitting={function(_id, _title, _desc) {
    var _contents = Array.from(this.state.contents);
    {/* _contents 안에 있는 원소들 중 우리가 수정하고자 하는 놈의 
       id값과 같은 id값을 가지는 원소를 찾는다. */}
    var i = 0;
    while(i < _contents.length) {
      if(_contents[i].id === _id) {
         {/* 찾은 놈의 id, title, desc 값을, UpdateContent에서 올라온 id, title, desc로 바꾼다. */}
        _contents[i] = {id:_id, title:_title, desc:_desc};
        break;
      }
      i = i + 1;
    }
    this.setState({
      {/* 수정한 _contents를 state에 다시 넣어준다. */}
      contents : _contents,
      mode : "read"
    });
  }.bind(this)}></UpdateContent>;
}

title input 위에 보면, hidden form을 사용해서 id 데이터를 보존하고 있다.
id는 내부적으로 현재 화면에 표시된 데이터가 몇 번째인지 식별하기 위해 필요한 데이터.
사용자에게 보일 필요가 없는 데이터이므로, hidden form을 이용하면 된다.

이 hidden form은, 현재 우리가 고치고 있는 content의 id를 기억해뒀다가
상위 component에 수정한 데이터와 함께 id를 그대로 다시 전달해주는 역할을 한다.

근데, 사실 여기서는 상위 component에 id를 전달할 때
state를 이용해서 전달하고 있으므로 hidden form이 필요하진 않음.
하지만 e.target.id.value를 통해서 전달하고 싶다면
hidden form을 사용할 수도 있다는 것을 알려주기 위함.

immutable 재언급

위의 코드에서 보면 원래 Create에서 concat으로 구현했던 내용을
Array.from을 이용해서 구현하고 있는 것을 볼 수 있음.

원본을 복사한 새로운 배열을 만들어서, 그 놈을 수정하고 다시 집어넣어줬다.
-> 저번에 언급했던 immutable 테크닉.

이렇게 하면 직접 원본을 바꿀 때와 달리
‘변경 이전 state’와 ‘변경 이후 state’가 구분되기 때문에
shouldComponentUpdate를 사용할 수 있게 된다.