42일차 React Custom Component2
2021. 9. 16. 23:45
반응형
2021. 09. 15 수요일
1. Today's Key Points!🔑
- Autocomplete
- ClickToEdit
2. Sprint과제 복기!🧐
전날에이어 오늘은 Advanced를 복기해볼 것이다.
👉🏻Autocomplete
오토컴플릿 컴포넌트는 검색창에 input값을 입력하면 밑에 input 값과 유사한 추천 검색 옵션을 보여주는 자동 완성 기능이다. 이 부분에서는 상태를 관리해주는 적절한 로직을 잘 짜주는 것이 핵심이다.
import { useState, useEffect } from 'react';
import styled from 'styled-components';
const deselectedOptions = [
'rustic',
'antique',
'vinyl',
'vintage',
'refurbished',
'신품',
'빈티지',
'중고A급',
'중고B급',
'골동품'
];
const boxShadow = '0 4px 6px rgb(32 33 36 / 28%)';
const activeBorderRadius = '1rem 1rem 0 0';
const inactiveBorderRadius = '1rem 1rem 1rem 1rem';
export const InputContainer = styled.div`
margin-top: 8rem;
background-color: #ffffff;
display: flex;
flex-direction: row;
padding: 1rem;
border: 1px solid rgb(223, 225, 229);
border-radius: ${activeBorderRadius};
z-index: 3;
box-shadow: 0;
&:focus-within {
box-shadow: ${boxShadow};
}
> input {
flex: 1 0 0;
background-color: transparent;
border: none;
margin: 0;
padding: 0;
outline: none;
font-size: 16px;
}
> div.delete-button {
cursor: pointer;
}
`;
export const DropDownContainer = styled.ul`
background-color: #ffffff;
display: block;
margin-left: auto;
margin-right: auto;
list-style-type: none;
margin-block-start: 0;
margin-block-end: 0;
margin-inline-start: 0px;
margin-inline-end: 0px;
padding-inline-start: 0px;
margin-top: -1px;
padding: 0.5rem 0;
border: 1px solid rgb(223, 225, 229);
border-radius: 0 0 1rem 1rem;
box-shadow: ${boxShadow};
z-index: 3;
> li:hover {
background-color: lightgray;
}
> li {
padding: 0 1rem;
&.selected {
background-color: lightgray;
}
}
`;
export const Autocomplete = () => {
const [hasText, setHasText] = useState(false); //input값의 유무 상태
const [inputValue, setInputValue] = useState('');//input값의 상태
const [options, setOptions] = useState(deselectedOptions);//option의 상태는 input값을 포함하는 autocomplete 추천 항목 리스트를 확인하기 위함
const [selected, setSelected] = useState(-1);//키보드로 option 선택할때 필요한 selected상태
useEffect(() => {
if (inputValue === '') { //처음 렌더링 됐을 때의 상태와, input값을 모두 지워줬을 때
setHasText(false); //input값의 유무상태를 false(없음)으로
setOptions([]);//option은 빈배열로 만들어서 아래에 리스트가 나타나지 않도록 구현
}
if(inputValue !== ''){ //input값을 입력하면
setOptions(deselectedOptions.filter((el) => { //입력된 값을 포함하는 option만 걸러준 상태로 변경한다.
return el.includes(inputValue)
})
)
}
}, [inputValue]);
const handleInputChange = (event) => { //input값 변경 시 발생되는 이벤트 핸들러.
setInputValue(event.target.value); //inputValue를 입력된 값으로 바꿔준다.
setHasText(true); //input값 유무상태도 당연히 true(있음)으로 바꿔준다.
};
const handleDropDownClick = (clickedOption) => { //DropDown 컴포넌트의 li엘리먼트에서 onClick으로 이벤트 핸들러 함수에 option을 전달해주고 있다.
setInputValue(clickedOption) //전달받은 option으로 inputValue를 변경해준다.
};
const handleDeleteButtonClick = (event) => { //x 버튼 누르면
setInputValue(""); //input입력창을 비워준다.
};
const handleKeyUp = (event) => { //option을 키보드로 선택할 수 있게해주는 핸들러 함수
if(hasText){ //input에 값이 있을때
if(event.key === 'ArrowDown' && options.length - 1 > selected){
setSelected(selected + 1);
}
//options.length에 -1을 해주는 이유는 selected의 최대값을 맞춰주기 위해서이다.
//예를들어 밑에 option이 2개가 나왔다고 가정했을 때, selected값이 최대 1까지 변할 수 있게 해줘야한다.
//'ArrowDown'키를 누르면 selected는 0이 되고, 한번 더 누르면 1이 되고, 그 다음은 더이상 옵션이 없기 때문에 키가 안먹히게 해주는 것이다.
if(event.key === 'ArrowUp' && selected >= 0){ //처음 조건을 이해했다면 여기는 자연스럽게 이해될 것이다.
setSelected(selected - 1);
}
if(event.key === 'Enter' && selected >= 0){ //Enter키로 option 선택
handleDropDownClick(options[selected])
setSelected(-1); //Enter키를 눌러서 선택이 되면 다시 selected는 -1이 되야한다.
}
}
}
return (
<div className='autocomplete-wrapper'>
<InputContainer >
<input type="text"
value={inputValue}
defaultValue={inputValue}
onChange={handleInputChange}
onKeyUp={handleInputChange}>
</input>
<div className='delete-button'
onClick={handleDeleteButtonClick}
>×</div>
</InputContainer>
//input에 값이없으면 DropDown이 보이지 않게 해준 것이다.
{hasText && <DropDown options={options}
handleComboBox={handleDropDownClick}
selected={selected}/>}
//위의 코드는 다음과 같다.
{/*hasText ? <DropDown options={options}
handleComboBok={handleDropDownClick}
selected={selected}/>
: null */}
</div>
);
};
export const DropDown = ({ options, handleComboBox, selected }) => {
return (
<DropDownContainer>
{options.map((option, idx) => {
return <li
key={idx}
onClick={() => handleComboBox(option)}
className={selected === idx ? 'selected' : ''}
>{option}</li>
})}
</DropDownContainer>
);
};
options를 변경해주는 부분을 useEffect에서 관리하니까 그냥 inputValue가 변할때 마다 알아서 options가 바뀌다보니 편하게 관리해줄 수 있었다. useEffect에서 관리를 해주지 않았다면 inputValue를 변하게 해주는 함수마다 options도 변하게 해주는 함수를 구현해주었어야 할 것이다.
👉🏻ClickToEdit
ClickToEdit 컴포넌트는 input 창을 클릭하면 수정이 가능하고, input 창이 아닌 곳을 클릭하면 수정한 내용이 반영되는 기능을 가진 컴포넌트이다. 이 부분에서는 useRef를 사용해서 input창을 클릭했을 때만 값을 바꿀 수 있게 해주는 것이 핵심이다.
import { useEffect, useState, useRef } from 'react';
import styled from 'styled-components';
export const InputViewContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
position: absolute;
/* left: 42%; */
`
export const InputBox = styled.div`
display: flex;
justify-content: center;
align-items: center;
/* text-align: center; */
/* display: inline-block; */
width: 150px;
height: 30px;
border: 1px #bbb dashed;
border-radius: 10px;
margin-left: 1rem;
`;
export const InputEdit = styled.input`
display: flex;
justify-content: center;
align-items: center;
text-align: center;
display: inline-block;
width: 150px;
height: 30px;
`;
export const InputView = styled.div`
display: flex;
justify-content: center;
text-align: center;
align-items: center;
margin: 1rem;
div.view {
margin-top: 0.5rem;
}
`;
//MyInput 컴포넌트는 ClickToEdit 컴포넌트의 자식 컴포넌트이다.
//그래서 value를 전달 받는데 여기(value)에는 { name, age } 로 name상태값과 age상태값을 가지고 있다.
export const MyInput = ({ value, handleValueChange }) => {
const inputEl = useRef(null);
const [isEditMode, setEditMode] = useState(false); //edit모드 상태
const [newValue, setNewValue] = useState(value); //출력값 상태
useEffect(() => {
if (isEditMode) { //edit모드가 활성화 되면 input창에 포커스를 줘서 수정이 가능하도록 해준다.
inputEl.current.focus();
}
}, [isEditMode]);
useEffect(() => {
setNewValue(value);
}, [value]);
const handleClick = () => { //span태그를 클릭하면 edit모드가 활성화 되고 위의 useEffect에 의해 input창에 포커싱이 된다.
setEditMode(true);
};
const handleBlur = () => { //input창이 아닌 다른 곳을 클릭하면 edit모드를 비활성화로 만든다.
setEditMode(false);
handleValueChange(newValue); //그리고 input창에 입력되어있는 값으로 newValue를 바꿔준다.
};
const handleInputChange = (e) => {
setNewValue(e.target.value); //input에 입력한 값을 newValue에 담아둔다.
//여기서 입력을 해준다고 바로바로 밑의 출력값이 변하지 않는다.
//왜냐하면 handleBlur에 의해서 handleValueChange 함수가 실행되어야 값이 바뀌기 때문이다.
};
return (
<InputBox>
//edit모드가 활성화 되면 input태그가 되고, 비활성이 되면 span태그가 된다.
{isEditMode ? (
<InputEdit
type='text'
value={newValue}
ref={inputEl}
onBlur={handleBlur}
onChange={handleInputChange}
/>
) : (
<span onClick={handleClick}>{newValue}</span>
)}
</InputBox>
);
}
const cache = {
name: '홍길동',
age: 18
};
export const ClickToEdit = () => {
const [name, setName] = useState(cache.name);
const [age, setAge] = useState(cache.age);
return (
<>
<InputViewContainer>
<InputView>
<label>이름</label>
<MyInput value={name} handleValueChange={(newValue) => setName(newValue)} />
</InputView>
<InputView>
<label>나이</label>
<MyInput value={age} handleValueChange={(newValue) => setAge(newValue)} />
</InputView>
<InputView>
<div className='view'>이름 : {name} / 나이 : {age}</div>
</InputView>
</InputViewContainer>
</>
);
};
Autocomplete를 구현하는 것 보다는 상대적으로 수월했다. 그리고 이 컴포넌트의 CSS는 app.css를 건드리면서 맞춰준 CSS라서 app.css를 적절하게 바꿔주지 않은 상태에서 위의 코드를 그대로 사용하면 디자인이 이상해질 수 있음을 주의하자.
반응형
LIST
'코드스테이츠 수강 TIL > Section 2' 카테고리의 다른 글
45일차 CodeStates HA2 섹션2 회고 (0) | 2021.09.28 |
---|---|
43, 44일차 React 상태관리, Redux (0) | 2021.09.17 |
41일차 React Custom Component (0) | 2021.09.16 |
40일차 컴포넌트 단위로 개발하기 (0) | 2021.09.15 |
39일차 StatesAirline Server (0) | 2021.09.11 |