Logo

React로 양식(form) UI 구현하기

양식(form)은 웹 애플리케이션에서 사용자로 부터 데이터를 입력 받기 위해 필수적인 UI 요소이죠? 그런데 리액트(React)로 직접 양식 UI를 구현해보면 처음에 생각했던 것보다 고려해야 할 부분이 많다는 것을 깨닫게 되는데요.

이번 포스팅에서는 비밀번호를 변경하기 위한 전형적인 양식 UI를 React 컴포넌트로 구현하면서 어떤 부분에 신경을 써서 프로그래밍을 해야 하는지 알아보겠습니다!

JSX 마크업

먼저 하나의 입력란과 제출 버튼으로 구성된 간단한 양식에 대한 마크업을 작성해보겠습니다. <form/> 부모 엘리먼트 아래에 <input/><button/> 엘리멘트를 자식으로 추가합니다.

(본 포스팅에서는 기능적인 부분에 초점을 맞출 것이기 때문에 CSS와 같은 시각적인 측면은 배재하도록 하겠습니다.)

import React from "react";

function PasswordUpdate() {
  return (
    <form>
      <input type="password" name="password" />
      <button type="submit">비밀번호 변경</button>
    </form>
  );
}

이 상태에서 버튼을 클릭해보면 화면만 깜빡이고 아무 것도 일어나지 않을 것입니다.

입력값 상태 관리

사용자가 입력한 비밀번호를 React가 인지하고 통제할 수 있도록 해줘야 합니다. React의 useState() 훅(hook)을 사용하면 함수 컴포넌트에 간단하게 상태를 추가해줄 수 있습니다.

입력란에서 입력값이 바뀔 때 마다 발생하는 변경(change) 이벤트를 처리하기 위한 handleChange() 함수를 작성합니다. 그리고 입력란(<input/>)의 value 속성에 상태값을 설정하고, onChange 속성에 handleChange() 함수를 설정해줍니다.

useState 훅이 생소하신 분들은 관련 포스팅를 참고 바랍니다.

import React, { useState } from "react";

function PasswordUpdate() {
  const [password, setPassword] = useState("");

  const handleChange = (event) => setPassword(event.target.value);

  return (
    <form>
      <input type="password" value={password} onChange={handleChange} />
      <button type="submit">비밀번호 변경</button>
    </form>
  );
}

제출 이밴트 처리

제출 버튼을 클릭하면 양식(form)에서 제출(submit) 이벤트가 발생합니다. 이 이벤트를 처리하기 위한 handleSubmit() 함수를 작성하고, 양식(<form/>)의 onSubmit 속성에 설정해줍니다.

양식을 제출할 때는 기본적으로 브라우저에서 새로 고침이 일어나는데, SPA(single page application)에서는 필요가 없는 동작으로 event.preventDefault()로 방지합니다. 입력된 패스워드는 브라우저에 내장된 alert() 함수를 통해 알람창으로 띄우도록 하겠습니다.

import React, { useState } from "react";

function PasswordUpdate() {
  const [password, setPassword] = useState("");

  const handleChange = (event) => setPassword(event.target.value);

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`변경된 비밀번호: ${password}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="password"
        name="password"
        value={password}
        onChange={handleChange}
      />
      <button type="submit">비밀번호 변경</button>
    </form>
  );
}

중복 제출 방지

사용자가 양식에서 제출 버튼을 클릭하면 서버나 외부 API를 호출하는 경우가 많은데 어쩔 수 없이 약간의 지연이 발생하게 됩니다. 사용자가 현재 진행 중인 제출 이벤트 처리가 미처 종료되기 전에 제출 버튼을 또 클릭할 경우, 양식이 중복 제출되는 문제가 발생할 수 있습니다. 따라서 사용자가 제출 버튼을 클릭하지 마자, 제출 버튼을 비활성화 시켰다가, 이벤트 처리가 완료되었을 때, 제출 버튼을 다시 활성화 시켜주는 것이 안전합니다.

먼저 버튼의 활성화 여부를 관리하기 위한 disabled 상태를 추가하고, 제출 버튼(<button/>)의 disabled 속성에 설정해줍니다. 그리고 handleSubmit() 함수에서 disabled 상태값을 맨 처음에는 true로 변경하였다가 맨 마지막에는 false로 변경해줍니다. 마지막으로 제출 이벤트 처리 시 발생하는 지연을 시뮬레이션 하기 위해서 handleSubmit() 함수 내에서 일부로 1초 지연을 발생시킵니다.

import React, { useState } from "react";

function PasswordUpdate() {
  const [password, setPassword] = useState("");
  const [disabled, setDisabled] = useState(false);

  const handleChange = ({ target: { value } }) => setPassword(value);

  const handleSubmit = async (event) => {
    setDisabled(true);
    event.preventDefault();
    if (password.length < 8) {
      alert("8자 이상의 비밀번호를 사용하셔야 합니다.");
    } else {
      await new Promise((r) => setTimeout(r, 1_000));
      alert(`변경된 비밀번호: ${password}`);
    }
    setDisabled(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="password" value={password} onChange={handleChange} />
      <button type="submit" disabled={disabled}>
        비밀번호 변경
      </button>
    </form>
  );
}

이제 제출 버튼을 클릭해보면 1초 간 버튼이 비활성된 후에 알람창이 뜨고, 알림창을 닫으면 버튼이 다시 활성화 될 것입니다.

입력값 유효성 검증

일반적으로 양식에서 사용자가 잘못된 값을 입력하는 경우 입력값을 처리하지 않고 피드백을 줘야 합니다. 예제 양식에서는 8자리 이상의 비밀번호가 입력되었는지 검증하는 코드를 handleSubmit() 함수에 삽입하도록 하겠습니다.

import React, { useState } from "react";

function PasswordUpdate() {
  const [password, setPassword] = useState("");
  const [disabled, setDisabled] = useState(false);

  const handleChange = ({ target: { value } }) => setPassword(value);

  const handleSubmit = async (event) => {
    setDisabled(true);
    event.preventDefault();
    if (password.length < 8) {
      alert("8자의 이상의 비밀번호를 사용하셔야 합니다.");
    } else {
      await new Promise((r) => setTimeout(r, 1000));
      alert(`변경된 비밀번호: ${password}`);
    }
    setDisabled(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="password" value={password} onChange={handleChange} />
      <button type="submit" disabled={disabled}>
        비밀번호 변경
      </button>
    </form>
  );
}

이제 비밀번호를 입력하지 않거나, 8자보다 짧은 비밀번호를 입력하고 양식을 제출할 경우, 알람창에 에러 메시지가 표시될 것입니다.

입력란이 많은 양식의 경우에는 위와 같이 제출 버튼을 클릭할 때 뿐만 아니라, 하나의 입력란에 입력을 마치고 다음 입력란으로 넘어갈 때 피드백을 주는 편이 사용자 입장에서 더 나은 경험이 될 수 있습니다. 이렇게 실시간 입력값 검증이 필요한 경우에는 각 입력란에서 blur 이벤트가 발생할 때 입력값 검증을 처리해주면 됩니다.

전체 코드

본 포스팅에서 작성한 코드는 아래에서 확인하고 직접 실행해보실 수 있습니다.

마치면서

지금까지 간단한 예제를 통해서 React를 사용해서 실제로 어떤 식으로 양식 UI를 구현할 수 있는지에 대해서 살펴보았습니다.

참고로 실제 프로젝트에서는 이렇게 양식 UI를 밑바닥 부터 직접 개발하기 보다는 React Hook Form과 같은 라이브러리도 많이 사용합니다. 다음 포스팅에서는 커스텀 훅(hook)을 이용해서 이렇게 복잡해지는 양식 관련 코드를 컴포넌트로 부터 분리하는 방법에 대해서 다뤄보도록 하겠습니다.