1. Input 작성으로 책 추가
1) ➕ 플러스 버튼 눌렀을 때
(1) 하단에 input 창 띄우고 아이콘 ☑️ 버튼으로 변경
input 창 띄우기
⓵ true/false값을 저장하는 state 생성
let [plusBtn, setPlusBtn] = useState(false);
기본 값은 false
const openInput = () => {
setPlusBtn(true);
};
<div className={`registration ${plusBtn ? "on" : ""}`}>
// false 일때
.registration{
display: none;
}
//true 일때
.registration.on {
display: block;
}
처음 class registration는 안보여야 하니께 display:none 상태
<FontAwesomeIcon
icon={plusBtn ? faCircleCheck : faCirclePlus}
className="circlePlus cursor"
onClick={openInput}
/>
(2) 포커스가 인풋 창에 뜨기
💡 DOM을 직접 선택하기 보단 ref를 사용 권장
⓵ useRef로 input DOM을 선택할 변수를 저장
const titleInputRef = useRef(null);
⓶ input에 ref 지정
ref={titleInputRef}
⓷ useEffect로 plusBtn state가 변경 될 때마다 렌더링
useEffect(() => {
titleInputRef.current.focus();
}, [plusBtn]);
(3) ⛔️ 삭제 버튼 색 회색으로 변하고 클릭이 안됨
⓵ 위와 같은 방식으로 true 일 때 class에 on 붙이기
<FontAwesomeIcon
icon={faSquareMinus}
className={`squareMinus cursor ${plusBtn ? "on" : ""}`}
/>
⓶ pointer-events 이벤트 제어 속성으로 클릭 막기
.squareMinus.on {
color: $subG;
pointer-events: none;
}
💡 체크☑️ 버튼 누르면 다시 이벤트 제어 풀리고 색 원상복구 됨
(4) 지우개 표시 버튼 눌렀을 때, value값 초기화
⓵ input value 값을 저장하는 state 생성
let [inputValue, setInputValue] = useState();
⓶ input에 변화(onChange)가 생길 때마다 함수 실행
const inputText = (e) => {
setInputValue(e.target.value);
};
💡 e.target(event가 발생한 DOM = input DOM), e.target.value(input 입력값)을 의미
inputText() : setter함수[setInputValue]로 InputValue 상태값을 e.target.value(input 입력값)로 업데이트
💡 왜 onChange를 해야하는가! input에 value 속성만 지정하게 되면 그 값으로만 값을 조절할 수 있는 권한이 있다. 그럼 사용자는 값을 입력하는 권한이 없어 input 안에 값 입력이 불가능하다.
⓷ input에 onChange와 value 값 넣기
<input
type="text"
placeholder="읽을 책을 입력하세요"
value={inputValue}
// e 안쓰면 텍스트 입력 안됨
onChange={inputText}/>
💡 그럼 input에 value 왜 적냐!
react가 '단일 소스 진리' 원칙을 따르기 때문.
react는 컴포넌트의 상태(state)와 속성(props) 사용
-> value를 추가 안하면 input의 값이 state와 일치하지 않아 사용자 입력추적 어려움
So, react에서 input을 제어할 때, value 속성을 state와 연결해 react가 사용자 입력을 추적하고, ui 업데이트 가능케 해야함
⓸ button을 눌렀을 때(onClick) inputValue 상태 값을 ""(null)로 만드는 함수 생성
const clearAllText = () => {
setInputValue("");
};
⓹ button에 적용
<button className="clearbtn" onClick={clearAllText}>
🚫 이 과정 중에 생긴 오류가 있었다!! 뮈치겠다 진짜
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
: 이 오류는 React 컴포넌트에서 무한 루프(infinite loop)가 발생하여 React가 렌더링을 계속 반복하고 있을 때 발생한다.
일반적으로 이 오류는 다음과 같은 상황에서 발생합니다.
- state나 props가 변경될 때 렌더링이 계속 발생하는 경우
- 렌더링이 발생하는 함수 내에서 상태를 변경하거나 props를 변경하는 경우
- 조건문, 반복문 등의 제어문 안에서 렌더링이 발생하는 경우
일반적인 해결 방법은 다음과 같습니다.
- 상태(state)나 속성(props)이 변경될 때 불필요한 렌더링을 방지하기 위해 shouldComponentUpdate() 함수를 사용하거나 PureComponent를 사용합니다.
- 상태(state)를 변경할 때 setState() 함수 대신에 setState() 함수의 콜백 함수를 사용합니다.
- 조건문이나 반복문 내에서 렌더링이 발생하는 경우, 조건문이나 반복문을 밖으로 이동시키고, 조건문이나 반복문의 결과에 따라 렌더링을 처리합니다.
암튼 이유는 ⓷⓹에서 onClick={clearAllText()},onChange={inputText()} 이렇게 작성했기 때문-> 이건 콜백함수도 뭣도 아님
-> onClick={clearAllText} Or onClick={()=>{clearAllText()} 이렇게 작성해야함
(5) ❌ 표시 버튼 눌렀을 때 창 닫힘
⓵ plusBtn 상태 값을 false로 변경
const closeInput = () => {
setPlusBtn(false);
};
<button className="inputCloseBtn" onClick={closeInput}>
2) 체크☑️ 버튼 눌렀을 때
(1) input에 입력을 모음/자음만 하면 alert 창 띄우기
[전체 코드]
const addBook = () => {
if (pattern.test(inputValue)) {
alert("똑바로 작성해주세요");
setInputValue("");
return titleInputRef.current.focus();
}
};
⓵ 자음/모음을 검사하는 정규식을 저장
let pattern = /([^가-힣a-z\x20])/i;
⓶ test 메서드로 inputValue가 pattern 정규식에 만족하는지 판별한다.
if (pattern.test(inputValue)) {
}
⓷ false일 때 alert창을 띄우고 inputValue 상태 값을 비운다.
if (pattern.test(inputValue)) {
alert("똑바로 작성해주세요");
setInputValue("");
}
⓸ 상태 값을 비우고 그 input 칸에 focusing 한다.
if (pattern.test(inputValue)) {
alert("똑바로 작성해주세요");
setInputValue("");
return titleInputRef.current.focus();
}
(2) input에 아무것도 입력을 안했을 때 alert 창 띄우기
[전체코드]
const addBook = () => {
// if (pattern.test(inputValue)) {
//alert("똑바로 작성해주세요");
// setInputValue("");
// return titleInputRef.current.focus();
// }
if (plusBtn) {
if (!inputValue) {
alert("도서를 입력해주세요");
return titleInputRef.current.focus();
}
}
};
⓵ plusBtn이 true일 때
if (plusBtn) {
}
⓶ inputValue 값이 false 이면
if (plusBtn) {
if (!inputValue) {
}
}
💡 plusBtn이 true일 때 조건을 하나 더 만든 이유 :
조건을 추가하지않으면 버튼을 누른 후 input 창이 이미 inputValue 값이 false이기 때문에 도서를 입력하라는 alert창이 먼저 출력된다.
⓷ alert창을 띄우고 focusing 해라
if (plusBtn) {
if (!inputValue) {
alert("도서를 입력해주세요");
return titleInputRef.current.focus();
}
}
(3) 입력한 value 포스트잇으로 보여주기
[전체코드]
const addBook = () => {
// if (pattern.test(inputValue)) {
// alert("똑바로 작성해주세요");
// setInputValue("");
// return titleInputRef.current.focus();
//}
//if (plusBtn) {
// if (!inputValue) {
// alert("도서를 입력해주세요");
// return titleInputRef.current.focus();
// }
let copy = [...listBook];
copy.unshift({ id: bookId, title: inputValue });
setListBook(copy);
increaseId();
setInputValue("");
}
};
⓵ 책 배열 깊은 복사
let copy = [...listBook];
⓶ 새로 추가되는 object 앞에 추가하기
copy.unshift({ id: bookId, title: inputValue });
⓷ setListBook copy 대입
setListBook(copy);
💡 ⓸ id 값 부여
- id값 state에 저장
const [bookId, setBookId] = useState(listBook.length);
💡 ⓹ id 값 증가
const increaseId = () => {
if (plusBtn) {
let copy = bookId;
copy++;
setBookId(copy);
}
};
- plusBtn이 true일 때 : ☑️ btn을 눌렀을 때
- bookId 값을 1 증가
increaseId();
⓺ inputValue 상태 값 비우기
setInputValue("");
(4) 엔터했을 때 도서 추가
const addEnter = () => {
if (window.event.keyCode === 13) {
addBook();
}
};
<input
type="text"
placeholder="읽을 책을 입력하세요"
value={inputValue}
onChange={(e) => inputText(e)}
ref={titleInputRef}
onKeyPress={addEnter}
/>
(5) 도서 추가 후 다시 인풋에 포커싱
useEffect(() => {
titleInputRef.current.focus();
}, [plusBtn, listBook]);
[전체 코드]
app.js
import { useState, useRef, useEffect } from "react";
import "./css/reset.css";
import "./css/common.scss";
import book from "./data/Book";
// fontawesome
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCirclePlus,
faCircleCheck,
faSquareMinus,
faMagnifyingGlass,
faXmark,
faEraser,
} from "@fortawesome/free-solid-svg-icons";
function App() {
// 변수
let pattern = /([^가-힣a-z\x20])/i;
// ---usestate
// plus button
let [plusBtn, setPlusBtn] = useState(false);
// input value
let [inputValue, setInputValue] = useState("");
// book data
let [listBook, setListBook] = useState(book);
//book id
const [bookId, setBookId] = useState(listBook.length);
//------------useref
const titleInputRef = useRef(null);
// ---------------------useEffect
// input focus
useEffect(() => {
titleInputRef.current.focus();
}, [plusBtn, listBook]);
// --------함수 생성
//도서 추가
const addBook = () => {
if (pattern.test(inputValue)) {
alert("똑바로 작성해주세요");
setInputValue("");
return titleInputRef.current.focus();
}
if (plusBtn) {
if (!inputValue) {
alert("도서를 입력해주세요");
return titleInputRef.current.focus();
}
let copy = [...listBook];
copy.unshift({ id: bookId, title: inputValue });
setListBook(copy);
increaseId();
setInputValue("");
}
};
// 엔터로 도서 추가
const addEnter = () => {
if (window.event.keyCode === 13) {
addBook();
}
};
// input 하단에 띄우기
const openInput = () => {
setPlusBtn(true);
// testWord();
addBook();
};
//input value change
const inputText = (e) => {
setInputValue(e.target.value);
};
//input value null
const clearAllText = () => {
setInputValue("");
};
// listBook.id lncrease
const increaseId = () => {
if (plusBtn) {
let copy = bookId;
copy++;
setBookId(copy);
}
};
// input close
const closeInput = () => {
setPlusBtn(false);
};
// 자음, 모음 검사
const testWord = () => {
if (pattern.test(inputValue)) {
alert("똑바로 작성해주세요");
setInputValue("");
return titleInputRef.current.focus();
}
};
// 빈칸 검사
const blankWord = () => {
if (inputValue === "") {
alert("도서를 입력해주세요");
return titleInputRef.current.focus();
}
};
return (
<div className="App">
<div className="bg center">
<div className="container">
<h1 className="title">2023 독서 계획</h1>
<p className="msg">2023년에 읽을 도서</p>
<div className="icon">
{" "}
<FontAwesomeIcon
icon={plusBtn ? faCircleCheck : faCirclePlus}
className="circlePlus cursor"
onClick={openInput}
/>
<FontAwesomeIcon
icon={faSquareMinus}
className={`squareMinus cursor ${plusBtn ? "on" : ""}`}
/>
</div>
<div className={`registration ${plusBtn ? "on" : ""}`}>
<input
type="text"
placeholder="읽을 책을 입력하세요"
value={inputValue}
onChange={(e) => inputText(e)}
ref={titleInputRef}
onKeyPress={addEnter}
/>
<button className="clearBtn" onClick={clearAllText}>
{" "}
<FontAwesomeIcon icon={faEraser} className="faEraser cursor" />
</button>
<button className="inputCloseBtn" onClick={closeInput}>
{" "}
<FontAwesomeIcon icon={faXmark} className="faXmark cursor" />
</button>
</div>
<div className="category">
<div className="menu">
<ul>
<li>전체보기</li>
<li>즐겨찾기</li>
</ul>
</div>
<div className="search">
{" "}
<FontAwesomeIcon
icon={faMagnifyingGlass}
className="faMagnifyingGlass cursor"
/>
</div>
</div>
<div className="row flex-row wrap">
{listBook.map((book, i) => {
return (
<div className="bucket img2 center ">
<h4>{book.title}</h4>
</div>
);
})}
</div>
</div>
</div>
</div>
);
}
export default App;
➕ (22.02.20) app.js 쓴 것들 component화 하기 ------------------------
(1) main으로 따로 컴포넌트화 시킴
[Main.js]
import React from "react";
import { useState, useRef, useEffect } from "react";
import book from "../../data/Book";
import "./main.scss";
// fontawesome
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCirclePlus,
faCircleCheck,
faSquareMinus,
faMagnifyingGlass,
faXmark,
faEraser,
} from "@fortawesome/free-solid-svg-icons";
const Main = () => {
// 변수
let pattern = /([^가-힣a-z\x20])/i;
// ---usestate
// plus button
let [plusBtn, setPlusBtn] = useState(false);
// input value
let [inputValue, setInputValue] = useState("");
// book data
let [listBook, setListBook] = useState(book);
//book id
const [bookId, setBookId] = useState(listBook.length);
//------------useref
const titleInputRef = useRef(null);
// ---------------------useEffect
// input focus
useEffect(() => {
titleInputRef.current.focus();
}, [plusBtn, listBook]);
// --------함수 생성
//도서 추가
const addBook = () => {
if (pattern.test(inputValue)) {
alert("똑바로 작성해주세요");
setInputValue("");
return titleInputRef.current.focus();
}
if (plusBtn) {
if (!inputValue) {
alert("도서를 입력해주세요");
return titleInputRef.current.focus();
}
let copy = [...listBook];
copy.unshift({ id: bookId, title: inputValue });
setListBook(copy);
increaseId();
setInputValue("");
}
};
// 엔터로 도서 추가
const addEnter = () => {
if (window.event.keyCode === 13) {
addBook();
}
};
// input 하단에 띄우기
const openInput = () => {
setPlusBtn(true);
// testWord();
addBook();
};
//input value change
const inputText = (e) => {
setInputValue(e.target.value);
};
//input value null
const clearAllText = () => {
setInputValue("");
};
// listBook.id lncrease
const increaseId = () => {
if (plusBtn) {
let copy = bookId;
copy++;
setBookId(copy);
}
};
// input close
const closeInput = () => {
setPlusBtn(false);
};
return (
<div className="main">
{" "}
<div className="bg center">
{" "}
<div className="container">
{" "}
<p className="msg">2023년에 읽을 도서</p>
<div className="icon">
{" "}
<FontAwesomeIcon
icon={plusBtn ? faCircleCheck : faCirclePlus}
className="circlePlus cursor"
onClick={openInput}
/>
<FontAwesomeIcon
icon={faSquareMinus}
className={`squareMinus cursor ${plusBtn ? "on" : ""}`}
/>
</div>
<div className={`registration ${plusBtn ? "on" : ""}`}>
<input
type="text"
placeholder="읽을 책을 입력하세요"
value={inputValue}
onChange={(e) => inputText(e)}
ref={titleInputRef}
onKeyPress={addEnter}
/>
<button className="clearBtn" onClick={clearAllText}>
{" "}
<FontAwesomeIcon icon={faEraser} className="faEraser cursor" />
</button>
<button className="inputCloseBtn" onClick={closeInput}>
{" "}
<FontAwesomeIcon icon={faXmark} className="faXmark cursor" />
</button>
</div>
<div className="category">
<div className="menu">
<ul>
<li>전체보기</li>
<li>즐겨찾기</li>
</ul>
</div>
<div className="search">
{" "}
<FontAwesomeIcon
icon={faMagnifyingGlass}
className="faMagnifyingGlass cursor"
/>
</div>
</div>
<div className="row flex-row wrap">
{listBook.map((book, i) => {
return (
<div className="bucket img2 center ">
<h4>{book.title}</h4>
</div>
);
})}
</div>
</div>
</div>
</div>
);
};
export default Main;
(2) 메인 타이틀 클릭 시 이동할 수 있도록 수정
<span
className="cursor"
onClick={() => {
navigate("/");
}}
>
2023 독서 계획
</span>
<Routes>
{" "}
<Route path="/" element={<Main />}>
{" "}
</Route>
<Route path="/bookSearch" element={<BookSearch />}>
{" "}
</Route>
<Route path="*" element={<div>없는 페이지에요</div>} />
</Routes>{" "}
(3) scss도 따로 폴더 정리
'프론트엔드로 가는 길 > portfolio 제작 여정기 👩🏻💻' 카테고리의 다른 글
04. 자살예방웹사이트 - Authentication로 로그인 제작 (0) | 2023.09.01 |
---|---|
01. 자살예방웹사이트 - Firebase로 hosting하기 (0) | 2023.08.20 |
04 BookBucket - 내가 읽을 책을 localStorage에 저장하자!📚[7.20] (0) | 2023.07.20 |
03 BookBucket - 내가 읽지 않을 책을 삭제하자!📚[2.21] (2) | 2023.02.22 |
02 BookBucket - 내가 추가할 책을 참고해보자!📚[2.20] (2) | 2023.02.21 |