안녕하세요! 오늘은 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
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;
useState
훅을 사용해 입력된 텍스트(inputText
)와 태그 리스트(tags
)를 관리합니다.handleTagAdd
함수는 폼 제출 이벤트를 막고, 입력된 텍스트를 태그 리스트에 추가합니다.handleTagDelete
함수는 주어진 인덱스의 태그를 리스트에서 제거합니다.handleInputChange
함수는 입력된 텍스트 값을 업데이트합니다.handleInputKeyPress
함수는 엔터 키를 눌렀을 때 태그를 추가합니다.태그 컴포넌트의 스타일링을 위해 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; /* 테두리 없애기 */
}
이제 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;
useState
훅을 사용해 폼의 입력값을 관리합니다.handleSubmit
함수는 폼 제출 이벤트를 처리하고 입력값 검증을 수행합니다.합니다.
발행 폼 컴포넌트의 스타일링을 위해 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;
}
태그를 입력하고 관리할 수 있는 폼을 만들어보았습니다! 이 폼은 엔터 키를 눌러 태그를 추가하고, 삭제 버튼을 통해 태그를 삭제가 가능합니다. 이러한 해시태그를 구현해보고 싶었는데 만들게 되어서 즐거웠습니다😆