React 로 Agree Checkbox 구현기
다양한 서비스의 회원가입 페이지를 보면 위의 이미지처럼 동의 체크 버튼 그룹이 있다. 맨 위의 CheckBox 는 보통 '전체 or 모두 동의하기' 버튼이고 아래 CheckBox 는 필수 동의 사항이거나 선택 동의 사항이다.
많이 접해서 친숙해보이지만 구현이 은근 간단하지 않다.
1) '모두 동의하기' 체크시 하단의 checkbox 들이 전부 체크가 되어야함.
2) '모두 동의하기' 체크 해지시 하단의 checkbox 가 전부 체크 해제 되어야함.
3) 모두 체크된 상태에서 하단의 checkbox가 하나라도 해지가 되면 '모두 동의하기' 는 다시 체크 해제 되어야함.
4) 모두 체크되지 않은 상태에서 하단의 checkbox가 모두 체크가 될 경우 '모두 동의하기'가 체크가 되어야 함.
이것을 jquery 로 구현했을 때에는 하단의 동의 버튼들의 갯수를 세고
체크된 체크박스 수를 처음에는 0으로 놓은 다음 체크 될 때마다 하나씩 카운트 하여 하단의 동의 버튼 총 갯수와 일치하면 '모두 동의하기' 버튼 체크, 아니면 체크 해제 이렇게 구현했다.
리액트에서는 이부분을 어떻게 구현하면 좋을까?
나는 checkbox와 label 은 묶어서 하나의 컴포넌트(CheckBox)로 만들어 놓았다.
[CheckBox 컴포넌트 모습]
이 컴포넌트는 코드를 따로 첨부하지는 않겠다.
3개의 체크박스가 있지만 크게 둘로 나눌 수 있다. '모두 동의하기 버튼' 과 '모두 동의하기'가 아닌 버튼으로. '필수', '선택' 도 이번 글에는 다루지 않고 하나로 묶어서 보겠다.
마크업을 아래와 같이 했다. '모두 동의하기' 버튼과 '모두 동의가 아닌 버튼'으로 분리했다.
<div className="agree">
<CheckBox
name="agreement"
value="all"
checked={allIsChecked}
className="round"
onCheck={onAllCheck}
>
모두 동의하기
</CheckBox>
{smallCheckBoxs.map((item) => (
<CheckBox
key={item.value}
name={item.name}
value={item.value}
checked={item.checked}
className={item.className}
onCheck={onSingleCheck}
>
{item.children}
</CheckBox>
))}
</div>
'모두 동의하기'가 아닌 버튼은 배열에 담아 반복문으로 컴포넌트를 뽑아냈다. 예시에는 하단의 동의 버튼이 2개 이지만 경우에 따라 늘어날 수 있기 때문에 배열에 담아 반복문으로 돌리는 것이 좋을것 같았다.
const [smallCheckBoxs, setSmallCheckBoxs] = useState([
{
name: 'agreement',
value: 'check1',
children: '서비스 이용약관 및 개인정보 처리방침에 동의합니다. (필수)',
checked: false,
className: 'round small',
},
{
name: 'agreement',
value: 'check2',
children: '마케팅 정보 수신에 동의합니다. (선택)',
checked: false,
className: 'round small',
},
]);
디자인만 보면 '모두 동의하기' 버튼도 포함하여 전부 배열에 넣어서 map 으로 돌리고, '모두 동의하기' 만 style 을 다르게 주면 될것 같다. 그러나 '모두 동의하기' 와 아닌 버튼이 체크 됐을때 동작이 서로 다르기 때문에 아에 분리하는것이 낫다는 판단이 들었다.
'모두 동의하기' 를 체크시 onAllCheck 함수가 동작하고
아닌 버튼을 체크시 onSingleCheck 이다.
// 체크박스 단일 선택
const onSingleCheck = (e: ChangeEvent<HTMLInputElement>) => {
const targetValue = e.currentTarget.value;
setSmallCheckBoxs(
smallCheckBoxs.map((item) =>
targetValue === item.value
? { ...item, checked: !item.checked }
: { ...item },
),
);
};
//체크박스 전체 선택
const onAllCheck = (e: ChangeEvent<HTMLInputElement>) => {
setAllIsChecked((prev) => !prev);
if (e.target.checked) {
setSmallCheckBoxs(
smallCheckBoxs.map((item) => ({ ...item, checked: true })),
);
} else {
setSmallCheckBoxs(
smallCheckBoxs.map((item) => ({ ...item, checked: false })),
);
}
};
onSingleCheck 는 checkbox를 클릭/클릭해제 하면 해당 체크박스가 체크/체크해제 되는 코드이다.
onAllCheck는 '모두 동의하기' 를 클릭/클릭해제 하면 해당 Checkbox가 체크/체크해제 되고 하단의 체크 박스를 모두 체크/체크해제 하는 코드이다.
그렇다면 구현내용 중에 1,2번은 구현되었다. 3,4번은 어떻게 구현할수 있을까?
하단의 체크 박스가 모두 체크되면 '모두 동의하기' 버튼이 체크되고, 하나라도 체크해제 되면 '모두 동의하기' 버튼은 해제된다.
useEffect(() => {
setAllIsChecked(smallCheckBoxs.every((item) => item.checked));
}, [smallCheckBoxs]);
useEffect hooks 을 활용하여 하단의 체크박스들의 체크 여부를 smallCheckBoxs 값이 변경 될 때마다 체크한다.
배열 메서드인 every를 활용해서 하단 동의 버튼들의 전체 체크 여부를 Boolean 값으로 받는다. 그 값을 setAllIsChecked 에 넣어주면 된다.
allIsChecked 이 true 일때 input[type="checkbox"] 이 check 되고
false 일 때 check가 해제 된다.
const [allIsChecked, setAllIsChecked] = useState(false);
...
<div className="agree">
<CheckBox
name="agreement"
value="all"
checked={allIsChecked}
className="round"
onCheck={onAllCheck}
>
모두 동의하기
</CheckBox>
</div>
다음에는 react-hook-form 을 이용해서 FORM 을 구현해 보겠다.