미리보기
open/close - useState로 상태 변경
조건부렌더링 - open상태만 modal컴포넌트 렌더링
// styled ...
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
// onClick 시, isOpen 상태 변경하는 헨들러
const openModalHandler = () => {
setIsOpen(!isOpen);
};
return (
<>
<ModalContainer>
<ModalBtn
// 클릭 시, Modal이 열린 상태(isOpen)를 boolean 타입으로 변경하는 메소드
onClick={openModalHandler}
>
{isOpen === true ? "Opened!" : "Open Modal"}
{/* 조건부 렌더링 - Modal 열린 상태(isOpen true)면 내부 텍스트 'Opened!'/ 닫힌 상태(isOpen false)면 'Open Modal' */}
</ModalBtn>
{/* 조건부 렌더링 - Modal 열린 상태(isOpen true)만 모달창과 배경 출력 */}
{isOpen && (
<ModalBackdrop onClick={openModalHandler}>
{/* 부모 클릭이벤트 물려받으므로, 안물려받는 코드 작성 */}
<ModalView onClick={(e) => e.stopPropagation()}>
<button onClick={openModalHandler}>×</button>
<span>HELLO MODAL 🔥</span>
<div> </div>
</ModalView>
</ModalBackdrop>
)}
</ModalContainer>
</>
);
};
export const Toggle = ({ primary }) => {
const [isOn, setisOn] = useState(false);
const toggleHandler = () => {
setisOn(!isOn);
};
return (
<>
<ToggleContainer
// 클릭 시 isOn 상태 변경하는 메소드
onClick={toggleHandler}
>
{/* 조건부 스타일링 - Switch ON 상태만 toggle--checked 클래스를 div 엘리먼트 2개에 모두 추가*/}
<div className={`toggle-container ${isOn ? "toggle--checked" : ""}`} />
<div className={`toggle-circle ${isOn ? "toggle--checked" : ""}`} />
</ToggleContainer>
{/* 조건부 렌더링 - Switch ON상태면 내부 텍스트를 'ON'으로, 아니면 'OFF'*/}
<Desc>Toggle Switch {isOn ? "ON" : "OFF"}</Desc>
</>
);
};
map((el, idx)=> ....onClick={()=> handeler함수(idx) })
export const Tab = () => {
// currentTab: 현재 어떤 Tab이 선택됐는지 확인하기 위한 index상태 (초기값은 0)
const [currentTab, setCurrentTab] = useState(0);
const menuArr = [
{ name: "Tab1", content: "Tab menu ONE" },
{ name: "Tab2", content: "Tab menu TWO" },
{ name: "Tab3", content: "Tab menu THREE" },
];
// parameter로 현재 선택한 인덱스 값을 전달
const selectMenuHandler = (index) => {
setCurrentTab(index);
};
// map으로 li 출력
const menuTab = menuArr.map((el, index) => (
<li
key={index}
// map의 index 전달
onClick={() => selectMenuHandler(index)}
// 조건부렌더링 - 현재탭이면 focused 클래스 추가
className={`submenu${currentTab === index ? " focused" : ""}`}
>
{el.name}
</li>
));
return (
<>
<div>
<TabMenu>
{menuTab} // 출력
</TabMenu>
<Desc>
{/* 현재 선택된 메뉴의 content를 표시 */}
<p className="menuContent">{menuArr[currentTab].content}</p>
</Desc>
</div>
</>
);
};
아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행 안하기
e.target.value.trim() !== ""
e.key === "Enter"
or e.keyCode == 13
태그 추가하고 나서 input 창 비우기
e.target.value = "";
중복 태그인지 검사하여 이미 있으면 추가 안하기
if (tags.includes(e.target.value)) {
window.alert("중복된 태그입니다");
return setTags([...tags]);
}
태그 개수 10개로 제한
if (tags.length > 10) {
window.alert("더이상 태그를 추가할 수 없습니다!");
e.target.value = "";
}```
const removeTags = (indexToRemove) => {
const res = tags.filter((el, idx) => idx !== indexToRemove);
setTags(res);
}
...
<span
className="tag-close-icon"
onClick={() => removeTags(index)}
>
× // x 특수문자 사용법
</span>
export const Tag = () => {
const initialTags = ["CodeStates", "kimcoding"];
const [tags, setTags] = useState(initialTags);
// 삭제 - 클릭된 index 제거
const removeTags = (indexToRemove) => {
const res = tags.filter((el, idx) => idx !== indexToRemove);
setTags(res);
};
// 추가 - tags 배열에 새로운 태그 추가
const addTags = (e) => {
// 1. 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
// 2. 태그가 추가되면 input 창 비우기
if (e.key === "Enter" && e.target.value.trim() !== "") {
// 3. 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
if (tags.includes(e.target.value)) {
window.alert("중복된 태그입니다");
return setTags([...tags]);
}
// 태그 개수 10개로 제한
if (tags.length > 10) {
window.alert("더이상 태그를 추가할 수 없습니다!");
e.target.value = "";
}
// 새 태그 추가 - 배열 뒤에 추가
else {
setTags([...tags, e.target.value]);
e.target.value = "";
}
}
};
return (
<>
<TagsInput>
<ul id="tags">
{tags.map((tag, index) => (
<li key={index} className="tag">
<span className="tag-title">{tag}</span>
{/* 삭제 아이콘 click 시, removeTags 메소드 실행 */}
<span
className="tag-close-icon"
onClick={() => removeTags(index)}
>
×
</span>
</li>
))}
</ul>
<input
className="tag-input"
type="text"
// 엔더키 누르면 태그 추가, {addTags}만 전달해도 됨
onKeyUp={(e) => addTags(e)}
placeholder="Press enter to add tags"
/>
</TagsInput>
</>
);
};
filter
: 추천항목에 입력 값의 글자가 포함된 항목만 dropdown으로 출력export const Autocomplete = () => {
// hasText - input값의 유무를 확인
const [hasText, setHasText] = useState(false);
// inputValue - input값의 상태를 확인
const [inputValue, setInputValue] = useState("");
// options - input값 포함하는 autocomplete 추천 항목 리스트를 확인
const [options, setOptions] = useState(deselectedOptions);
// useEffect를 아래와 같이 활용 가능
useEffect(() => {
if (inputValue === "") {
setHasText(false);
}
}, [inputValue]);
// input과 dropdown 상태 관리를 위한 handler
const handleInputChange = (event) => {
/* - input 값 변경(onChange)시 발생하는 이벤트 핸들러.
* - input값과 상태를 연결시키는 controlled component.
* - 추천항목이 dropdown으로 바로 바뀌는 상태로 변경.
*
* 1. input값 상태인 inputValue 갱신
* 2. input값 유무 상태인 hasText 갱신
* 3. options의 상태(추천항목)따라 dropdown으로 보이는 항목 갱신
*/
const dropDownList = deselectedOptions.filter((el) =>
el.includes(event.target.value)
);
setInputValue(event.target.value);
setHasText(true);
setOptions(dropDownList);
};
// 상하 화살표 키로 input 값 변경하기
// 인덱스 0부터고, 처음시작할땐 아무것도 선택되지 않은 -1을 초기값 지정
// 아래키누르면 0으로 바뀌면서 options[0] 선택하도록
// 위 키누르면 index - 1 해주기
const [selected, setSelected] = useState(-1);
const handleDropDownClick = (clickedOption) => {
/* - dropdown 항목 클릭 시(onClick)실행되는 이벤트 헨들러
* - dropdown 항목 클릭 시 input값이 해당 항목의 값으로 변경
*
* 1. input값 상태인 inputValue 갱신
* 2. options의 상태가 클릭된 항목만으로 갱신
*/
setInputValue(clickedOption);
setOptions([clickedOption]);
};
const handleDeleteButtonClick = () => {
// - X버튼 클릭 시(onClick) input값 전부 삭제하는 이벤트 헨들러
// 1. input값 상태인 inputValue를 빈 문자열로 초기화
setInputValue("");
};
const handleKeyUp = (e) => {
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState#example
// eslint-disable-next-line
if (
e.getModifierState("Fn") ||
e.getModifierState("Hyper") ||
e.getModifierState("OS") ||
e.getModifierState("Super") ||
e.getModifierState("Win")
)
return;
if (
e.getModifierState("Control") +
e.getModifierState("Alt") +
e.getModifierState("Meta") >
1
)
return;
if (e.code === "ArrowUp" && selected >= 0) {
setSelected(selected - 1);
}
if (e.code === "ArrowDown" && selected < options.length - 1) {
setSelected(selected + 1);
}
if (e.code === "Enter" && selected >= 0) {
handleDropDownClick(options[selected]);
setSelected(-1);
}
};
return (
<div className="autocomplete-wrapper">
<InputContainer>
{/* input값(value) - state와 연결
input값 변경 시 handleInputChange 함수 호출 */}
{/* 삭제 버튼 - input 값, dropdown 삭제하는 handler 함수 */}
<input
type="text"
value={inputValue}
onChange={(e) => handleInputChange(e)}
onKeyUp={(e) => handleKeyUp(e)}
></input>
<div onClick={handleDeleteButtonClick} className="delete-button">
×
</div>
</InputContainer>
{/* 조건부 렌더링 - input 값 없으면? dropdown 미출력 */}
<DropDown
options={hasText ? options : []}
handleComboBox={handleDropDownClick}
/>
</div>
);
};
export const DropDown = ({ options, handleComboBox }) => {
const dropDown = options.map((el, index) => (
<li onClick={() => handleComboBox(el)} key={index}>
{el}
</li>
));
return (
<DropDownContainer>
{/* input 값에 맞는 autocomplete 선택 옵션 보여주기*/}
{dropDown}
</DropDownContainer>
);
};
export const MyInput = ({ value, handleValueChange }) => {
const inputEl = useRef(null);
const [isEditMode, setEditMode] = useState(false);
const [newValue, setNewValue] = useState(value);
// 수정가능 상태일 시, 포커스 두기
useEffect(() => {
if (isEditMode) {
inputEl.current.focus();
}
}, [isEditMode]);
useEffect(() => {
setNewValue(value);
}, [value]);
// 수정가능 상태 - isEditMode 변경
const handleClick = () => {
setEditMode(true);
};
// 포커스 해제하면 - 수정불가능 상태로 변경 + newValue값 전달
const handleBlur = () => {
handleValueChange(newValue); // newValue값 전달
setEditMode(false); // 수정불가능 상태
};
// input 입력시(onChange) - 저장된 value 입력값으로 갱신
const handleInputChange = (e) => {
setNewValue(e.target.value);
};
return (
<InputBox>
{isEditMode ? (
<InputEdit
type="text"
value={newValue}
ref={inputEl}
// 포커스를 잃으면, Edit가 불가능한 상태로 변경되는 메소드
onBlur={handleBlur}
// input 값 변경되면, 저장된 value를 업데이트하는 메소드
onChange={handleInputChange}
/>
) : (
<span
// input 글자 클릭시 - 수정 가능한 상태로 변경
onClick={handleClick}
>
{newValue}
</span>
)}
</InputBox>
);
};
const cache = {
name: "김코딩",
age: 20,
};
export const ClickToEdit = () => {
const [name, setName] = useState(cache.name);
const [age, setAge] = useState(cache.age);
return (
<>
<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>
</>
);
};