- 전체 클릭하면 아래의 카테고리에 전부 체크.
- 전체 체크된 상태에서 또 체크하면 전체 체크 사라짐.
- 아래 카테고리들이 각자 체크된 상태를 인지해야 함.
├ 부모 컴포넌트(LayoutThird.js)
=import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import SmallModal from '../SmallModal/SmallModal';
import CheckBox from './Child/CheckBox';
function LayOutThird({ checkBoxTitle, setCheckBoxTitle }) {
const navigate = useNavigate();
const [totalCheck, setTotalCheck] = useState(false);
const [eachCheck, setEachCheck] = useState(false);
const [names, setNames] = useState([]);
function historyHandler() {
const temptArr = [];
let query = `category=`;
names.forEach(name => {
if (name === '펜션') {
temptArr.push('pension');
}
if (name === '게스트하우스') {
temptArr.push('guest');
}
if (name === '호텔') {
temptArr.push('hotel');
}
if (name === '렌탈 하우스') {
temptArr.push('rental');
}
});
let subquery = '';
temptArr.forEach(data => {
subquery += `${data},`;
});
query += subquery;
navigate(`/list?${query}`);
}
function changetitle() {
if (names.length === 0) {
setCheckBoxTitle('스테이 유형');
}
if (names.length > 1) {
setCheckBoxTitle(`${names[0]}외 ${names.length - 1}`);
}
if (names.length === 1) {
setCheckBoxTitle(`${names[0]}`);
}
if (checkBoxTitle === '전체') {
setCheckBoxTitle('전체');
}
}
function resetTitle() {
if (totalCheck) {
setCheckBoxTitle('전체');
return;
}
if (!totalCheck) {
setCheckBoxTitle('-');
}
}
function resetNames() {
if (!totalCheck && checkBoxTitle === '-') {
setNames([]);
}
}
useEffect(() => {
resetNames();
resetTitle();
}, [totalCheck]);
useEffect(() => {
setCheckBoxTitle('스테이 유형');
}, []);
function closeModal(e) {
const parentStyleVisible =
e.target.parentElement.parentElement.style.visibility;
let judge = false;
if (parentStyleVisible === 'visible') {
judge = true;
}
e.target.parentElement.parentElement.style.visibility = judge && 'hidden';
}
return (
<section>
<SmallModal title="스테이 유형" />
<button
style={styles}
onClick={e => {
resetNames();
changetitle();
closeModal(e);
historyHandler();
}}
>
적용하기
</button>
<section style={{ marginTop: 20 }}>
<CheckBox content="전체" checked={setTotalCheck} total={totalCheck} />
{mokDataCategorie.map(data => (
<CheckBox
key={data.id}
setNames={setNames}
content={data.name}
checked={setEachCheck}
total={totalCheck}
/>
))}
</section>
</section>
);
}
export default LayOutThird;
const styles = {
margin: 'auto',
border: 'none',
borderRadius: 50,
paddingTop: 10,
paddingRight: 53,
paddingBottom: 10,
paddingLeft: 53,
width: 'max-content',
backgroundColor: 'black',
color: 'whitesmoke',
};
const mokDataCategorie = [
{ id: 4, name: '펜션' },
{ id: 1, name: '게스트하우스' },
{ id: 2, name: '호텔' },
{ id: 3, name: '렌탈 하우스' },
];
간단설명
- totalCheck: 전체 체크인지
- eachCheck: 각각 체크인지
- name: 각각 체크된 카테고리의 이름을 돌려줌(ex. [게스트하우스, 펜션])
├ 자식 컴포넌트(CheckBox.js)
import { useEffect } from 'react';
import style from './CheckBox.module.css';
const arr = [];
function CheckBox({ setNames, content, checked, total }) {
function check(e) {
checked(e.target.checked);
if (!arr.includes(content) && content !== '전체' && e.target.checked) {
arr.push(content);
setNames(arr);
}
if (content !== '전체' && !e.target.checked) {
arr.splice(arr.indexOf(content), 1);
setNames(arr);
}
}
useEffect(() => {
if (!total && arr.length !== 0) {
arr.length = 0;
}
}, [total]);
return total ? (
<section className={style.checkBoxWap}>
<input
type="checkbox"
id="check"
checked={true}
onChange={e => check(e)}
/>
<label htmlFor="check">
<div className={style.content}>{content}</div>
<span>{}</span>
</label>
</section>
) : (
<section className={style.checkBoxWap}>
<input
type="checkbox"
id={`checkbox${content}`}
onChange={e => {
check(e);
}}
/>
<label htmlFor={`checkbox${content}`}>
<div className={style.content}>{content}</div>
<span>{}</span>
</label>
</section>
);
}
export default CheckBox;
간단설명
- 전체 체크일때는 전체가 체크된 부분이 Rendering
- 전체체크가 False면 각각 체크 가능
문제상황
- 각각 체크가 공통이라(게스트 하우스, 펜션 등 뭐를 눌렀는지 알 수가 없음)
- 전체 체크를 하면
Warning: Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info:- rendering이 이상함.(체크 버튼 2개까지는 인지, 3개부터 인지못함...)
├ 부모 컴포넌트(LayoutThird.js)
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import SmallModal from '../SmallModal/SmallModal';
import CheckBox from './Child/CheckBox';
import BlackButton from '../../../../../components/BlackButton/BlackButton';
function LayOutThird({ setCheckBoxTitle, onHidden }) {
const navigate = useNavigate();
const [totalCheck, setTotalCheck] = useState(false);
const [checkedCategoriesArr, setcheckedCategoriesArr] = useState([]);
const [checks, setChecks] = useState(
Array(mokDataCategorie.length).fill(false)
);
const allCheck = () => {
setChecks(Array(checks.length).fill(!totalCheck));
setTotalCheck(!totalCheck);
};
const individualCheck = index => {
setChecks(prev => {
const newArr = [...prev];
newArr[index] = !newArr[index];
return newArr;
});
};
const checkedCatogories = () => {
const temptArr = [];
!totalCheck &&
checks.forEach((data, index) => {
data && temptArr.push(mokDataCategorie[index].name);
});
setcheckedCategoriesArr(temptArr);
};
function historyHandler() {
let query = `category=`;
checkedCategoriesArr.forEach(name => {
if (name === '펜션') {
query += 'pension,';
}
if (name === '게스트하우스') {
query += 'guest,';
}
if (name === '호텔') {
query += 'hotel,';
}
if (name === '렌탈 하우스') {
query += 'rental,';
}
});
navigate(`/list?${query}`);
}
function changetitle() {
if (checkedCategoriesArr.length === 0) {
setCheckBoxTitle('스테이 유형');
}
if (checkedCategoriesArr.length > 1) {
setCheckBoxTitle(
`${checkedCategoriesArr[0]}외 ${checkedCategoriesArr.length - 1}`
);
}
if (checkedCategoriesArr.length === 1) {
setCheckBoxTitle(`${checkedCategoriesArr[0]}`);
}
}
function resetcheckedCategoriesArr() {
setcheckedCategoriesArr([]);
}
useEffect(() => {
checkedCatogories();
}, [checks]);
return (
<section>
<SmallModal title="스테이 유형" />
<BlackButton
content="적용하기"
onClick={e => {
checkedCatogories();
changetitle();
onHidden('hidden');
historyHandler();
resetcheckedCategoriesArr();
}}
/>
<section style={{ marginTop: 20 }}>
<CheckBox content="전체" checked={totalCheck} onChecked={allCheck} />
{mokDataCategorie.map((data, index) => (
<CheckBox
key={data.id}
setcheckedCategoriesArr={setcheckedCategoriesArr}
content={data.name}
checked={checks[index]}
onChecked={() => individualCheck(index)}
total={totalCheck}
/>
))}
</section>
</section>
);
}
export default LayOutThird;
const mokDataCategorie = [
{ id: 4, name: '펜션' },
{ id: 1, name: '게스트하우스' },
{ id: 2, name: '호텔' },
{ id: 3, name: '렌탈 하우스' },
];
간단 설명
1. 각각 체크를 담당하는 array state를 관리 (checked)
2. 이 state로 전체 체크시 [true, true, true, true]로 변경
3. id로 인덱스를 정해서 [true, false, false, false]면 펜션만 선택된 것으로 인지
├ 자식 컴포넌트(CheckBox.js)
import style from './CheckBox.module.css';
function CheckBox({ content, checked, onChecked }) {
return (
<section className={style.checkBoxWap}>
<input
type="checkbox"
id={`checkbox${content}`}
checked={checked}
onChange={e => {
onChecked();
}}
/>
<label htmlFor={`checkbox${content}`}>
<div className={style.content}>{content}</div>
<span>{}</span>
</label>
</section>
);
}
export default CheckBox;
간단설명
아주 간단해짐!
크게는, input이나 버튼의 값을 e.target.value처럼 접근하는 방법밖에 생각하지 못해서 발생한 상황으로 보인다. 작게보면 eachChecks 작동이 이상함을 알면서도 굳이굳이 의심하지 않고 사용한 잘못도 있다.
한글 => 영어로 쿼리를 주고 받을때 전부깨져서 일일이 if문으로 치환했는데, 며칠전 다른 팀의 코드를 훔쳐보다가(?) 더 좋은 메소드를 찾아냈다.
decodeURI() , encodeURI()
멘토님짱