React로 엔터를 눌러 태그를 추가할 수 있는 폼 만들기

밤새·2024년 7월 3일
1

Back/Front

목록 보기
9/12
post-thumbnail

안녕하세요! 오늘은 React를 사용해서 태그 입력 폼을 만들어보려고 합니다. 엔터 키를 눌러 태그를 추가하고, 각 태그를 삭제할 수 있는 간단하지만 매우 유용한 기능을 구현해보겠습니다!

프로젝트 설정

먼저 React 프로젝트를 설정하고 필요한 파일을 생성해봅시다.

npx create-react-app tag-input-form
cd tag-input-form
mkdir src/components
touch src/components/TagInput.js
touch src/components/PublishForm.js

1. 태그 입력 컴포넌트(TagInput)

TagInput 컴포넌트는 태그를 입력하고 리스트로 관리하는 기능을 제공합니다. 추가적으로 react-icons 패키지를 사용해 삭제 아이콘을 추가할 예정입니다.

먼저 react-icons 패키지를 설치합니다

npm install react-icons

이제 TagInput.js 파일에 코드를 작성해봅시다.

import React, { useState } from "react";
import { FiX } from "react-icons/fi"; // react-icons에서 X 아이콘을 가져옵니다.
import "../../css/record/tag.css";

function TagInput({ tags, setTags }) {
  const [inputText, setInputText] = useState("");

  const handleTagAdd = (e) => {
    e.preventDefault(); // 폼의 기본 제출 동작을 막습니다.

    if (inputText.trim() !== "") {
      setTags([...tags, inputText.trim()]); // 입력된 내용을 태그로 추가합니다.
      setInputText("");
    }
  };

  const handleTagDelete = (index) => {
    const updatedTags = [...tags];
    updatedTags.splice(index, 1); // 해당 인덱스의 태그를 삭제합니다.
    setTags(updatedTags);
  };

  const handleInputChange = (e) => {
    setInputText(e.target.value);
  };

  const handleInputKeyPress = (e) => {
    if (e.key === "Enter") {
      e.preventDefault(); // 폼의 기본 제출 동작을 막습니다.
      handleTagAdd(e);
    }
  };

  return (
    <div id="container">
      <div id="outputContainer">
        {tags.map((tag, index) => (
          <div key={index} className="taggedContent">
            {tag}
            <span className="deleteButton" onClick={() => handleTagDelete(index)}>
              <FiX /> {/* react-icons의 FiX 아이콘을 사용합니다. */}
            </span>
          </div>
        ))}
        <div id="inputContainer">
          <input
            type="text"
            id="inputText"
            value={inputText}
            placeholder="태그 입력"
            onChange={handleInputChange}
            onKeyPress={handleInputKeyPress}
          />
        </div>
      </div>
    </div>
  );
}

export default TagInput;

코드 설명

  1. State 관리 : useState 훅을 사용해 입력된 텍스트(inputText)와 태그 리스트(tags)를 관리합니다.
  2. 태그 추가 : handleTagAdd 함수는 폼 제출 이벤트를 막고, 입력된 텍스트를 태그 리스트에 추가합니다.
  3. 태그 삭제 : handleTagDelete 함수는 주어진 인덱스의 태그를 리스트에서 제거합니다.
  4. 입력 변경 처리 : handleInputChange 함수는 입력된 텍스트 값을 업데이트합니다.
  5. 엔터 키 처리 : handleInputKeyPress 함수는 엔터 키를 눌렀을 때 태그를 추가합니다.

1-2. 스타일링

태그 컴포넌트의 스타일링을 위해 tag.css 파일을 작성합니다.

#container {
  width: 85%;
  display: flex;
  flex-wrap: wrap;
  align-items: center; /* 세로 중앙 정렬 */
}

#outputContainer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem; /* 태그 간격 조절 */
}

.taggedContent {
  display: inline-block;
  padding: 0 0.8rem 0 0;
  border-radius: 0.25rem;
  color: #ff53ba;
  font-weight: bold;
}

.deleteButton {
  margin-left: 0.3rem;
  cursor: pointer;
}

#inputContainer input {
  padding: 0.25rem 0.5rem;
  border: none !important; /* 테두리 없애기 */
  border-radius: 0.25rem;
  width: 100px; /* 너비 조절 */
  outline: none; /* 선택 시 테두리 제거 */
}

#inputText {
  border: none; /* 테두리 없애기 */
}

2. 발행 폼 컴포넌트(PublishForm)

이제 PublishForm 컴포넌트를 작성하여 태그 입력 컴포넌트를 포함한 전체 폼을 구성해보겠습니다.

PublishForm.js 파일에 다음 코드를 작성합니다:

import React, { useState } from "react";
import "../../css/record/publishForm.css";
import TagInput from "./TagInput"; // 태그 입력 컴포넌트를 가져옵니다.

const PublishForm = ({ onCancel, onPublish, challenge_name, difficulty, videoUrl }) => {
  const [title, setTitle] = useState("");
  const [nickname, setNickname] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [tags, setTags] = useState([]);

  const handleSubmit = (e) => {
    e.preventDefault();

    if (!title || !nickname || !email || !password) {
      alert("모든 필수 항목을 입력해주세요.");
      return;
    }

    if (!videoUrl) {
      console.error("Video URL is not set.");
      return;
    }

    const data = {
      videoUrl,
      levelId: difficulty,
      likeNum: 0,
      title,
      nickname,
      hashtag: tags.join("/") + "/" + challenge_name,
      email,
      password,
    };

    onPublish(data);
  };

  return (
    <div className="section">
      <div className="publish-form">
        <div className="publish">발행</div>
        <form onSubmit={handleSubmit}>
          <div className="input-group">
            <input
              type="text"
              id="title"
              value={title}
              placeholder="제목 입력"
              onChange={(e) => setTitle(e.target.value)}
            />
          </div>
          <div className="input-group">
            <label htmlFor="nickname" className="input-label">
              닉네임
            </label>
            <input
              type="text"
              id="nickname"
              value={nickname}
              placeholder="ex) 미림여신"
              onChange={(e) => setNickname(e.target.value)}
            />
          </div>
          <div className="input-group">
            <label htmlFor="hashtags" className="input-label">
              해시태그
            </label>
            <TagInput tags={tags} setTags={setTags} /> {/* TagInput 컴포넌트 사용 */}
          </div>
          <div className="input-group">
            <label htmlFor="password" className="input-label">
              비밀번호
            </label>
            <input
              type="password"
              value={password}
              placeholder="비밀번호를 입력해주세요."
              onChange={(e) => setPassword(e.target.value)}
            />
          </div>
          <p className="pass-description">
            **영상 수정을 할 때 비밀번호를 입력하면 수정할 수 있어요!
          </p>
          <div className="input-group">
            <label htmlFor="email" className="input-label">
              이메일
            </label>
            <input
              type="text"
              id="email"
              value={email}
              placeholder="구글 이메일을 입력해주세요."
              onChange={(e) => setEmail(e.target.value)}
            />
          </div>
          <p className="email-description">
            **이메일을 입력하면 영상을 받을 수 있어요!
          </p>
          <div className="button-group">
            <button type="button" onClick={onCancel} className="cancel">
              취소
            </button>
            <button
              type="submit"
              className="submit"
              disabled={!title || !nickname || !email || !password}
            >
              공개 발행
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default PublishForm;

코드 설명

  1. State 관리: useState 훅을 사용해 폼의 입력값을 관리합니다.
  2. 폼 제출 처리: handleSubmit 함수는 폼 제출 이벤트를 처리하고 입력값 검증을 수행합니다.
  3. 조건부 렌더링: 필수 입력값이 모두 입력되지 않은 경우 제출 버튼을 비활성화

합니다.

2-2. 스타일링

발행 폼 컴포넌트의 스타일링을 위해 PublishForm.css 파일을 작성합니다.

/* PublishForm.css */

.section {
  width: 100%;
  padding: 20px;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  flex: 1;
}

.publish-form {
  width: 600px;
  background-color: #ffffff;
  padding: 5px;
  box-sizing: border-box;
  flex: 1;
}

.publish {
  font-size: 15px;
  font-weight: bolder;
  margin-bottom: 3%;
}

.input-group {
  margin-top: 4%;
  display: flex;
  align-items: center;
}

.input-group p {
  font-size: 14px;
  color: #666666;
  margin-top: 5px;
}

.input-label {
  font-weight: bolder;
  color: #000000;
  width: 15%;
}

.input {
  flex: 1;
}

.input-group label {
  display: block;
  margin-bottom: 5px;
}

.input-group input[type="text"] {
  padding-left: 10px;
  font-size: 16px;
  font-weight: bold;
  border: 1px solid #cccccc;
  width: 85%;
  color: #ff53ba;
}

.input-group input[type="password"] {
  font-weight: bold;
  font-size: 16px;
  border: none; /* 테두리 없애기 */
  outline: none; /* 포커스 표시 제거 */
  width: 85%;
  color: #ff53ba;
}

#title {
  padding: 0 0 8px 0;
  font-size: 24px;
  border: none;
  outline: none;
}

#title::placeholder {
  color: #999999;
}

#nickname {
  padding: 0 0 0 0;
  border: none;
  outline: none;
}

#hashtags {
  border: none;
  outline: none;
}

#email {
  padding: 0 0 0 0;
  border: none;
  outline: none;
}

.pass-description,
.email-description {
  font-size: 13px;
  color: #666666;
  margin-top: 5px;
}

.button-group {
  margin-top: 7%;
  display: flex;
  justify-content: center;
}

.button-group button {
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  border-radius: 25px;
  color: #ffffff;
  margin: 0 5px;
}

.button-group .submit {
  font-weight: bold;
  background-color: #ff53ba;
  border: none;
}

.button-group .cancel {
  font-weight: bold;
  background-color: #ffffff;
  color: #ff53ba;
  border-color: #ff53ba;
}

.button-group button + button {
  margin-left: 10px;
}

결과

마무리

태그를 입력하고 관리할 수 있는 폼을 만들어보았습니다! 이 폼은 엔터 키를 눌러 태그를 추가하고, 삭제 버튼을 통해 태그를 삭제가 가능합니다. 이러한 해시태그를 구현해보고 싶었는데 만들게 되어서 즐거웠습니다😆

profile
프로젝트를 통해 배운 개념이나 겪은 문제점들을 정리하고, 회고록을 작성하며 성장해나가는 곳입니다 😊

0개의 댓글

관련 채용 정보