태그기능을 구현하기 전, velog의 콘솔창을 통해 작동원리에 대해 살펴봤는데 아래와 같은 구조로 이루어진 것을 확인했습니다.
enter
키를 누르면 태그 리스트에 추가됨내부의 input이 focus되었을 때 border-color
를 변경해주기 위해 :focus-within 가상선택자를 사용했습니다.
input의 값을 tagItem
이라는 state으로 저장하고,
엔터키를 눌렀을 때 아래와 같은 조건에 부합되면 태그들의 데이터를 담은 array인, tagList
를 추가되도록 구현했습니다.
input.value
문자열의 길이가 0이 아니면서
tagList
에 추가되지 않기 위한 조건입니다Enter Key
를 받았을 때
onSubmit
이벤트 헨들러를 사용하면 되었을 것 같습니다추가된 태그를 나타내는 div
는 형태는 동일하고 내용만 다르기 때문에 tagList
array를 map()
메서드를 통해 tagList
의 요소 개수만큼 반복해서 만들었습니다.
따라서, tagItem
, tagList
state를 관리하는 것이 핵심 구현사항이라고 볼 수 있습니다.
결국 Tag에서 중요하게 다룬 기능은 state 관리
였기 때문에 tagList
에 새로운 tagItem
을 추가해주는 것은 어렵지 않게 넘겼습니다만,
tagList
에서 tagItem을 삭제
할 때, 기존에 Vanilla JavaScript로만 구현했던 것만 떠올라 새로운 발상을 하는데 적은 어려움을 느꼈습니다.
{tagList.map((tagItem, index) => {
return (
<TagItem key={index}>
<Text>{tagItem}</Text>
<Button onClick={deleteTagItem}>X</Button>
</TagItem>
)
})}
여기서
{tagItem}
의 데이터 가져오기가 어려웠는데
이렇듯 동일한 컴포넌트를 반복해서 만들었기 때문에
useRef를 적용시켰더니, 어떤 태그의 X 버튼
을 눌러도 맨 마지막 요소만이 선택되었습니다.
e.target.parentElement.firstChild.innerText
더 나은 방법은 아직 찾지 못해서, 직접 event 객체를 통해 DOM에 접근해서 필터를 적용한 array로 대체했습니다.
import React, { useState } from 'react'
import styled from 'styled-components'
import Title from '../components/Title'
const Tag = () => {
const [tagItem, setTagItem] = useState('')
const [tagList, setTagList] = useState([])
const onKeyPress = e => {
if (e.target.value.length !== 0 && e.key === 'Enter') {
submitTagItem()
}
}
const submitTagItem = () => {
let updatedTagList = [...tagList]
updatedTagList.push(tagItem)
setTagList(updatedTagList)
setTagItem('')
}
const deleteTagItem = e => {
const deleteTagItem = e.target.parentElement.firstChild.innerText
const filteredTagList = tagList.filter(tagItem => tagItem !== deleteTagItem)
setTagList(filteredTagList)
}
return (
<WholeBox>
<Title text='Tag' />
<TagBox>
{tagList.map((tagItem, index) => {
return (
<TagItem key={index}>
<Text>{tagItem}</Text>
<Button onClick={deleteTagItem}>X</Button>
</TagItem>
)
})}
<TagInput
type='text'
placeholder='Press enter to add tags'
tabIndex={2}
onChange={e => setTagItem(e.target.value)}
value={tagItem}
onKeyPress={onKeyPress}
/>
</TagBox>
</WholeBox>
)
}
const WholeBox = styled.div`
padding: 10px;
height: 100vh;
`
const TagBox = styled.div`
display: flex;
align-items: center;
flex-wrap: wrap;
min-height: 50px;
margin: 10px;
padding: 0 10px;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 10px;
&:focus-within {
border-color: tomato;
}
`
const TagItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin: 5px;
padding: 5px;
background-color: tomato;
border-radius: 5px;
color: white;
font-size: 13px;
`
const Text = styled.span``
const Button = styled.button`
display: flex;
justify-content: center;
align-items: center;
width: 15px;
height: 15px;
margin-left: 5px;
background-color: white;
border-radius: 50%;
color: tomato;
`
const TagInput = styled.input`
display: inline-flex;
min-width: 150px;
background: transparent;
border: none;
outline: none;
cursor: text;
`
export default Tag