게시판에서 이벤트를 부여할때 조금 어려운 부분이 바로 체크박스 부분이 아닐까 싶다.
UseState, UseState말을 들어도 동적으로 페이지를 변환시킨다는 부분은 피부로 잘 와닿지 않는다.
그렇기에 두가지 방법으로 체크박스를 핸들링하는 방법이 있다.
useState
는 값을 변화 시키고 원하는 부분에 대해 리랜더링을 할 수 있도록 도와주는 함수형 컴포넌트중 하나이다.useState
를 이용하면 배열이나 객체의 값을 원하는대로 조정할 수 있다.
이를 이용하면 체크박스 역시 손쉽게 조정이 가능하다.
체크박스를 불러온 데이터를 통해 동적으로 그려주는 코드를 보자
{data.fetchBoards.slice(0,10).map(data => (
<Table_Body_literable>
<Table_Body_Checkbox ><input id={data.number} type="checkbox" onClick={handleCheck} checked={checked[data.number]}></input></Table_Body_Checkbox>
<Table_Body_Number >{data.number}</Table_Body_Number>
<Table_Body_Title>{data.title}</Table_Body_Title>
<Table_Body_Date>{data.createdAt.slice(0,10).replace(/-/gi, '.')}</Table_Body_Date>
</Table_Body_literable>
))}
Props
의 디스트럭처링 할당을 통해 gql query
로 불러온 data
라는 항목 중, fetchBoards
의 데이터를 10개만 가져 온 후, 그것을 Map.()함수를 통해 동적으로 그려 준다고 했을때
하나의 checkbox
에 대해 data
의 number
로 아이디를 할당한다. 이는 check
상태를 바꾸기 용이하게 하기 위해 부여하는것이다. 또한 onClick
이벤트와 checked={checked[data.number]}
를 주어 눌렀을때 이벤트와, 체크가 되었는지 안되어있는지 설정한다.
useState로 값을 용이하게 변경하기 위해 일단 현재 상태를 저장하는 useState()
를 선언하자.
그리고 헤더부분의 체크박스 부분 역시 useState로 저장한다.
const [checked, SetChecked] = useState({ //각 버튼들의 체크 상태 기록. });
const[checkedAll, setCheckedAll] = useState(false);//헤더 부분 체크박스
checked
의 기본값을 배열로 잡는다. 왜 배열로 잡는지 찬찬히 아래로 내려가며 설명하겠다.
const handleCheck = (e) => {
////체크 상태 변경
console.log(e.target.checked)
const newChecked = {...checked, [e.target.id] : e.target.checked}
SetChecked(newChecked);
console.log(newChecked);
///
const values = []
for(let i = 0 ; i<data.fetchBoards.length; i++){
values.push(newChecked[data.fetchBoards[i].number]); //새로운 배열에 값을 저장.
}
const filterValues = values.filter(data => data === true)//체크된 친구들만 저장해주자, 근데 길이의 값이 서로 같다면? 모조리 체크되어 있는것이다.
if(data.fetchBoards.length === filterValues.length){ //모조리 체크 된 상태라면 헤더의 체크박스를 처리해버리자.
setCheckedAll(true);
} else {
setCheckedAll(false);
}
}
const newChecked = {...checked, [e.target.id] : e.target.checked}
를 선언한다. newChecked
는 useState
로 선언한 기본값인 checked
의 배열을 복사 한 후, 만일 눌렀을 때 해당 체크박스의 id값이 존재하지 않는다면 해당 체크박스의 id를 키값으로 설정, 그 체크박스가 눌렸는지 안눌렸는지 e.target.checked
의 값으로 저장한다.
즉 만일 배열에 존재하지 않는 id = 101
인 체크박스를 눌렀을경우 id: 101, true
상태가 들어가게 되는 것이다.
그리고 SetChecked(newChecked);
로 이전에 checked
를 newChecked
로 바꾸게 되고, 다음 for문을 실행하게 된다.
const values = []
for(let i = 0 ; i<data.fetchBoards.length; i++){
values.push(newChecked[data.fetchBoards[i].number]); //새로운 배열에 값을 저장.
}
선언한 values
배열에 for문을 통해 불러온 data.fetchBoards의 데이터의 길이만큼 values
배열에 저장 한 뒤, Filter.()함수를 통해 체크된 배열만 반환하여 filterValues
에 저장한다.
이를 하는 이유는 맨 위 헤더 부분 역시 만약 바디 부분의 체크박스가 모두다 체크 될 경우 헤드 부분의 체크박스 역시 체크가 되도록 상태를 변하게 만들어야 하기 때문이다.
따라서 만약 true
값인 id
만 가져온 배열, filterValues
의 길이가 미리 가져온 data.fetchBoards
의 길이가 같다면 이는 바디 부분의 체크박스가 모두 체크되었다는 말이기에 그때 setCheckedAll(true);
를 하여 헤드 체크박스를 바꿔준다.
이제 바디부분의 체크박스의 checked
상태는 위에서 정의한 '상태'와 똑같아야 하기때문에
checked={checked[data.number]}
까지 넣어 주게 된다면 체크와 체크해제에 따라 checked
배열이 변화하고, 체크박스의 상태 역시 변한다.
여기까지가 바디 부분에서 체크박스 클릭시 헤드 부분까지 변화를 주는 부분이다.
이제 만약 헤드부분의 체크박스를 클릭 시, 바디부분의 모든 체크박스를 체크하거나 체크 해제를 하게 일괄변경 하려면 어떻게 해야할까?
const handleCheckAll = (e) => {
const newCheckAll = e.target.checked
if(newCheckAll){
let newCheck = {};
for(let i=0; i<data.fetchBoards.length ; i++){
newCheck = {...newCheck, [data.fetchBoards[i].number] : true};
}
console.log(newCheck);
SetChecked(newCheck);
setCheckedAll(true);
} else {
let newCheck = {};
for(let i=0; i<data.fetchBoards.length ; i++){
newCheck = {...newCheck, [data.fetchBoards[i].number] : false};
}
console.log(newCheck);
SetChecked(newCheck);
setCheckedAll(false);
}
}
위의 바디부분을 구현했다면 일괄선택, 선택해제는 비슷하게 생각하자.
handleCheckAll
의 경우에는 새로운 변수 const newCheckAll = e.target.checked
를 선언한다.
여기서 생각해보면 newCheckAll
은 헤더의 체크박스값만 확인하면 되기 때문에 따로 배열을 선언하지않고 해당 값을 바로 if문에 넣어 판별식을 적어 나가자.
즉 만약 newCheckAll
이 True
값. 체크된 상태라면,
let newCheck = {};
for(let i=0; i<data.fetchBoards.length ; i++){
newCheck = {...newCheck, [data.fetchBoards[i].number] : true};
}
배열 newCheck
에 현재 가져온 데이터의 체크박스에 해당하는 모든 id값들을 true상태로바꾸어 버린다.
여기서 Spread
연산자로 newCheck
를 넣은 이유는 반복문을 돌리면서 계속해서 값을 넣어나가기 때문이다.
그 뒤 SetChecked
와 setCheckedAll
에 원하는 값만 넣어주면 끝이다.
잘 작동한다. 이처럼 객체로 저장할 수 있으나 그 방법이 싫다면 간단하게 배열로 처리하는 방법도 있다.
일단 변수 선언부분은 설명을 생략하겠다
const[checkedAll, setCheckedAll] = useState(false);
const[checked, SetChecked] = useState([]); //이번엔 배열로.
const handleCheck = (e) => {
const number = Number(e.target.id);
////체크 상태 변경
let newChecked = [];
if(checked.includes(number)){
newChecked = checked.filter(data => data !== number)
SetChecked(newChecked);
console.log(checked);
} else {
newChecked = [...checked, number]
SetChecked(newChecked);
console.log(checked);
}
///
if(data.fetchBoards.length === newChecked.length){
setCheckedAll(true);
} else {
setCheckedAll(false);
}
}
배열은 생각보다 간단한데,
일단 newChecked
라는 하나의 비어있는 배열과 const number = Number(e.target.id);
라는 클릭한 것의 아이디를 저장해주는 변수를 선언해준다. 그렇다면 배열에 들어가 있다는 말은 즉 해당하는 아이디의 체크박스는 클릭된 상태란 뜻이다
만일 선택한 체크박스가 체크되있는 상태가 false상태라면 newChecked
에 해당 배열을 넣어주고 SetChecked
라는 이미 선언한 useState
를 통해 상태를 변경해주고, 이미 체크된 상태라면 checked
의 배열에서 해당 아이디값을 filter
를 통해 제거한다.
왜 필터로? : 원본데이터를 직접 조작하는건 불변성을 깨트리는 행위이기에.
const handleCheckAll = (e) => {
const newCheckAll = e.target.checked
if(newCheckAll){
const newCheck = data.fetchBoards.map(data => data.number)
SetChecked(newCheck);
setCheckedAll(true);
} else {
const newCheck =[]
SetChecked(newCheck);
setCheckedAll(false);
}
}
일괄 선택, 해제 역시 간단하게 구현이 가능한데, 만일 헤더부분의 체크박스를 클릭시(newCheckAll === true
) SetChecked
즉 checked
의 배열에 모든 아이디 값들을 넣고, setCheckedAll
을 true
상태로 만들고, 만일 해제 할 시, 빈배열을 checked
에 넣어주고 setCheckedAll
역시 false
로 설정하는것이다.
이제 이벤트는 모두 부여했다. 하지만 이것으로 true와 false로 check의 상태를 바꿔주기 위해선 어떻게 해야할까?
checked={checked.includes(data.number)}
바로 includes의 함수를 이용하면 매우 손쉽게 변환해버릴수있다.
만약 해당 배열에(checked) 원하는 배열의 요소(data.number)가 있다면(includes) true로, 없다면 false를 반환해 줄것이기 때문이다.
이것이 바로 useState를 사용하여 배열형식으로 만드는 방식이다.
체크박스는 아주 간단한 친구일줄 알았는데 조금 깊게 파고 들어가다보니 쉬운 친구가 아니라는것을 알게되었다. 주말에 체크박스에 관해 좀더 정리를 해봐야할것같다.