누구든지 하는 리액트 6편: input 상태 관리하기


이 튜토리얼은 10편으로 이뤄진 시리즈입니다. 이전 / 다음 편을 확인하시려면 목차를 확인하세요.

자, 우리가 지금까지 배웠던것들을 요약해봅시다.

  • 컴포넌트 만들기
  • props 와 state
  • LifeCycle API

딱히 배운게 그리 많지는 않죠? 그런데 이것만으로도 정말 많은 것들을 만들 수 있습니다!
리액트는, 그냥 자바스크립트와 가깝습니다. 자바스크립트를 잘 알고 있다면, 리액트 관련해서는 배울게 그리 많지는 않습니다.

앞으로 우리는 전화번호부 프로젝트를 만들어볼건데요, 이 프로젝트에서는 우리가 배웠던 지식들을 응용하여 다양한 작업들을 구현하고, input 상태를 관리하는 방법과 배열을 다루는 방법을 알아보겠습니다.

프로젝트 코드는 https://github.com/vlpt-playground/phone-book 에서 확인 할 수 있습니다.

프로젝트 생성하기

우리가 기존에 만들었던 프로젝트는 그대로 두고, 새 프로젝트를 만들겠습니다.

create-react-app phone-book

그리고, 해당 디렉토리를 VSCode 로 열고, 내부에서 yarn start 를 통해서 개발서버를 시작하세요.

첫번째 컴포넌트, PhoneForm 만들기

우리가 먼저 만들 컴포넌트는 PhoneForm 입니다. 이 컴포넌트에서는 사용자에게서 이름과 전화번호를 입력받을 것입니다. 아직 우리가 input 컴포넌트의 입력을 state 에 담는 방법에 대해선 알아보지 않았었지요? 한번 알아봅시다.

input 다루기

우선, src 디렉토리 내부에 components 라는 디렉토리를 만드세요. 그리고, 그 안에 PhoneForm.js 라는 파일을 만들어서 다음 코드를 입력하세요.

// file: src/components/PhoneForm.js
import React, { Component } from 'react';

class PhoneForm extends Component {
  state = {
    name: ''
  }
  handleChange = (e) => {
    this.setState({
      name: e.target.value
    })
  }
  render() {
    return (
      <form>
        <input
          placeholder="이름"
          value={this.state.name}
          onChange={this.handleChange}
        />
        <div>{this.state.name}</div>
      </form>
    );
  }
}

export default PhoneForm;

onChange 이벤트가 발생하면, e.target.value 값을 통하여 이벤트 객체에 담겨있는 현재의 텍스트 값을 읽어올 수 있습니다. 해당 값을 state 의 name 부분으로 설정하세요.
render 부분에서 input 을 렌더링 할 떄에는 value 값과 onChange 값을 넣어주었습니다. onChange 는 input 의 텍스트 값이 바뀔때마다 발생하는 이벤트입니다. 여기에 우리가 만들어둔 handleChange 를 설정했습니다. 그리고, 나중에 우리가 데이터를 등록하고나면 이 name 값을 공백으로 초기화 해줄건데요, 그렇게 초기화 됐을 때 input 에서도 반영이 되도록 value 를 설정해주었습니다.

그리고 그 하단에는 name 값이 잘 바뀌고 있는지 확인 할 수 있도록 값을 렌더링해주었습니다.

자~ 그러면 이 컴포넌트를 App 에서 보여줄게요.

// file: src/App.js
import React, { Component } from 'react';
import PhoneForm from './components/PhoneForm';


class App extends Component {
  render() {
    return (
      <div>
        <PhoneForm />
      </div>
    );
  }
}

export default App;

결과물이 잘 나타났나요? 한번 여러분들도 input 값을 수정해보세요. 하단에 잘 나오고있나요?

전화번호부에는 전화번호가 들어가야겠지요. input 을 하나 더 추가해주겠습니다. input 이 여러개일때는 어떻게 처리해야할까요? 다음 코드를 살펴보세요.

// file: src/components/PhoneForm.js
import React, { Component } from 'react';

class PhoneForm extends Component {
  state = {
    name: '',
    phone: ''
  }
  handleChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value
    });
  }
  render() {
    return (
      <form>
        <input
          placeholder="이름"
          value={this.state.name}
          onChange={this.handleChange}
          name="name"
        />
        <input
          placeholder="전화번호"
          value={this.state.phone}
          onChange={this.handleChange}
          name="phone"
        />
        <div>{this.state.name} {this.state.phone}</div>
      </form>
    );
  }
}

export default PhoneForm;

아마 또 다른 이벤트 핸들러 함수를 만들면 되지 않을까? 라고 생각하신 분들도 있을겁니다. 그 방법은 물론 나쁜 방법은 아닙니다만 더 나은 방법이 있습니다.

바로, input 의 name 속성을 사용하는건데요, render 부분에 보시면, 각 input 에 name 값을 부여해주었습니다. 이를 통하여 우리는 각 input 을 구분 할 수 있게 됐죠.

이 name 값은, event.target.name 을 통해서 조회 할 수 있습니다.

setState 내부에서 사용된 문법은 Computed property names 라는 문법입니다. 혹여나 key 부분에 [ ] 괄호가 사용된 것이 생소하다면 링크를 클릭해보세요.

자, 이제 결과물을 확인해보세요. 두 input이 제대로 작동하나요?

부모 컴포넌트에게 정보 전달하기

이제 state 안에 있는 값들을 부모 컴포넌트에게 전달해줄 차례입니다. 이러한 상황에는, 부모 컴포넌트에서 메소드를 만들고, 이 메소드를 자식에게 전달한 다음에 자식 내부에서 호출하는 방식을 사용합니다.

우리는 App 에서 handleCreate 라는 메소드를 만들고, 이를 PhoneForm 한테 전달해주겠습니다. 그리고, PhoneForm 쪽에서 버튼을 만들어서 submit 이 발생하면 props 로 받은 함수를 호출하여 App 에서 파라미터로 받은 값을 사용 할 수 있도록 하겠습니다.

우선 App 을 다음과 같이 수정하세요.

// file: src/App.js
import React, { Component } from 'react';
import PhoneForm from './components/PhoneForm';

class App extends Component {
  handleCreate = (data) => {
    console.log(data);
  }
  render() {
    return (
      <div>
        <PhoneForm
          onCreate={this.handleCreate}
        />
      </div>
    );
  }
}

export default App;

그 다음엔, PhoneForm 에서 버튼과 onSubmit 이벤트를 설정하겠습니다.

// file: src/components/PhoneForm.js
import React, { Component } from 'react';

class PhoneForm extends Component {
  state = {
    name: '',
    phone: ''
  }
  handleChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value
    })
  }
  handleSubmit = (e) => {
    // 페이지 리로딩 방지
    e.preventDefault();
    // 상태값을 onCreate 를 통하여 부모에게 전달
    this.props.onCreate(this.state);
    // 상태 초기화
    this.setState({
      name: '',
      phone: ''
    })
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          placeholder="이름"
          value={this.state.name}
          onChange={this.handleChange}
          name="name"
        />
        <input
          placeholder="전화번호"
          value={this.state.phone}
          onChange={this.handleChange}
          name="phone"
        />
        <button type="submit">등록</button>
      </form>
    );
  }
}

export default PhoneForm;

handleSubmit 함수를 확인해보세요. 맨 위에 e.preventDefault() 라는 함수가 호출됐죠? 이 뜻은, 원래 이벤트가 해야 하는 작업을 방지시킨다는 의미입니다. 원래는 form 에서 submit 이 발생하면 페이지를 다시 불러오게 되는데요, 그렇게 되면 우리가 지니고있는 상태를 다 잃어버리게 되니까 이를 통해서 방지해주었습니다.

그 다음에는, props 로 받은 onCreate 함수를 호출하고, 상태값을 초기화해주었습니다.

render 부분에서는 submit 버튼을 만들고, form 부분에 onSubmit 이벤트를 등록해주었습니다.

코드를 다 작성하셨으면, 제대로 작동하는지 확인해보세요.

정리

이제 input 은 어떻게 다뤄야 할 지 감을 잡으셨지요? 이제 App 컴포넌트에서 state 내부에 배열을 선언하고, 배열안에 PhoneForm 에서 입력한것들을 집어넣고 배열 내부의 데이터를 렌더링하는 방법을 알아보겠습니다.

  • name ahn

    좋은 글 감사합니다. 읽다 보니 오타를 발견했습니다. “방지해주었ㅅ브니다.”

    • 반영했습니다. 감사합니다 ^^

  • andy

    좋은글 감사합니다 그런데 Phone Info 가 아니고 Phone Form 아닌가요? ^^

    • 맞습니다. 오타 지적 감사합니다 🙂

  • 박송희

    감사합니다.

  • 김구연

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

    이부분에서 target이 왜나오는거고 무엇을 의미하는 건가요??ㅠㅠ
    (강의 너무 잘 보고 있습니다 감사합니다!)

  • 조성원

    onChange 기능을사용하면 딜레이가생기는데 유효성검사를할때 무슨속성을쓰는게 좋을까요?

    • 딜레이가 발생한다는게, 살짝 끊김현상이 있으시다는건가요?
      일반적으로는 없을텐데.. 유효성검사가 오래걸리는거라면 throttle 이나 debounce 를 사용해서 유효성검사횟수를 제한시키는것도 좋은 방안이 될 수 있습니다.

  • 이가인

    this.props.onCreate(this.state);
    이 부분에서 onCreate is not a function 에러가 나는데 왤까요ㅠㅠ

    • 이가인

      제가 빼먹은 부분이 있었네요ㅠㅠ 좋은 강좌 감사합니다!

  • 이민수

    저번 컴포넌트 강좌에서도 그렇고 이번 강좌에서도 같은 현상이 발생하는데요!
    console.log가 안 나오네요..
    개발자도구 console 창에서 확인하고 픈데..
    로그가 하나도 안 올라오네요 ㅠㅠ.

    • 오잉 ㅜ 잘나오는뎅..

    • 김종학

      submit 버튼의 props 에
      onClick={this.handleSubmit}
      을 추가해보시면 될 듯 합니다..

  • Cui zhe

    정말 좋은 글 입니다.
    그런데 한가지 질문입니다.
    [e.target.name]: e.target.value
    왜 이렇게 해야만 하는지 설명 부탁드립니다.
    e.target.name: e.target.value 로 하니 오류가 나왔습니다.
    []는 무엇을 의미 하나요?

    • 변수 값을 키로 쓸 수 있습니다
      var sName = ‘happy’
      [ sName ] : value ==> happy : value

      • Cui zhe

        네, 그렇군요. 감사합니다.

  • 김종학

    안녕하세요, 잘배우고 갑니다!
    그런데 submit 버튼에
    onClick={this.handleSubmit} 을 추가해야하는것이 아닌지 궁금해서 코멘트 남깁니다.
    handleSubmit 을 별도 호출하지 않으면, 동작을 하지 않는거 같아서요.

  • 박진영

    질문하나 드려봅니다.
    굳이 폼 서브밋으로 해야하는이유가 있는건가요?
    그냥 버튼에 클릭이벤트에 함수연결해서 작성해도 같은코드같은데..
    웹시작한지 얼마안되서 어떤 규약이 있는건지 궁금하네요

  • 유창완

    요약하자면,
    onSubmit -> handleSubmit ->onCreate ->handleCreate
    이런 과정을 거치는 거군요?

    그런데 onCreate는 onSubmit 처럼 원래 정의되어있는 메서드가 아닌 것같네요?
    onCreate 대신 다른 이름으로 바꿔봤는데도 작동하네요.

    이런 상황에서는 onCreate라고 이름을 짓는 게 관습인가요?

    • 정동건

      OnCreate 부분은 Props의 이름을 정의하는거에요 변수처럼요! OnCreate말고 OnONON이렇게도 가능하겠죠

  • WoogieBoogie

    안녕하세요. 질문할 것이 한가지 있는데요.
    onSubmit 일 때 input태그의 value값을 리셋해주고 싶은데요.
    input태그의 value값을 this.state.value값으로 할 시에는 입력하나하나에
    render함수가 동작을 해버려서요… render함수를 불필요하게 실행시키고 싶지 않습니다.
    onSubmit할시에 input태그에 직접 DOM접근해서 input태그의 value값을 리셋시키자니
    직접 DOM접근하는게 뭔가 맞지않은 것 같기도 하고….

    너무 고민입니다….ㅠㅠㅠㅠ

  • DOBBY

    이거 지금 여쭤보면 답글 달아주시려나…ㅜ
    지금 리액트 훅스로 변경해보고 있는데 PhoneForm에서 App으로 데이터를 전달하는 과정에서
    onCreate가 함수가 아니라는 오류가 발생해서요…

    const PhoneForm = (onCreate) => {
    const [name, setName] = useState(“”);
    const [phone, setPhone] = useState(“”);

    const handleChange = (e) => {
    “name” === e.target.name && setName(e.target.value);
    “phone” === e.target.name && setPhone(e.target.value);
    };

    const handleSubmit = (e) => {
    //페이지 리로딩 방지
    e.preventDefault();
    //상태값 전달
    onCreate({ name: name, phone: phone });
    //상태값 초기화
    setName(“”);
    setPhone(“”);
    };

    이렇게 작성했는데 왜 그럴까요…?