리액트 라이프사이클, 체크박스(all포함), NextUI
"상태가 바뀌면 함수 컴포넌트가 리랜더링 된다"
유저친화적인 애니메이션을 함께 쓰려고 NextUI를 이용하기로 했고, 이 컴포넌트를 사용하여 개인정보동의 페이지를 제작해야했다.
1. NextUI 문서보기
NextUI가 기본적으로 주는 골격은 아래와 같다.
import React from "react";
import {CheckboxGroup, Checkbox} from "@nextui-org/react";
export default function App() {
const [selected, setSelected] = React.useState(["buenos-aires", "sydney"]);
return (
<div className="flex flex-col gap-3">
<CheckboxGroup
label="Select cities"
color="warning"
value={selected}
onValueChange={setSelected}
>
<Checkbox value="buenos-aires">Buenos Aires</Checkbox>
<Checkbox value="sydney">Sydney</Checkbox>
<Checkbox value="san-francisco">San Francisco</Checkbox>
</CheckboxGroup>
<p className="text-default-500 text-small">Selected: {selected.join(", ")}</p>
</div>
);
}
그리고 boolean 값으로 컨트롤도 가능하다
import React from "react";
import {Checkbox} from "@nextui-org/react";
export default function App() {
const [isSelected, setIsSelected] = React.useState(false);
return (
<div className="flex flex-col gap-2">
<Checkbox isSelected={isSelected} onValueChange={setIsSelected}>
Subscribe (controlled)
</Checkbox>
<p className="text-default-500">
Selected: {isSelected ? "true" : "false"}
</p>
</div>
);
}
2. NextUI로 골격만들기
이제 내가 만들어야 하는 UI 는 아래와 같다.
따라서 골격을 먼저 만들어주면 아래와 같다.
<div className="flex-col w-[360px] h-[160px] mt-6 ">
<Checkbox isSelected={isSelectedAll} onValueChange={handleCheckAll}>
아래 항목에 전부 동의합니다.
</Checkbox>
<CheckboxGroup label="" color="success" value={checkItems} onValueChange={setCheckItems}>
<Checkbox value="one" className="text-[0.875rem]">
(필수) 만 14세 이상입니다.
</Checkbox>
<Checkbox value="two">(필수) 이용약관에 동의합니다.</Checkbox>
<Checkbox value="three">(필수) 개인정보의 수집 및 이용에 동의합니다.</Checkbox>
<Checkbox value="four">(필수) 개인정보의 제3자가 제공에 동의합니다.</Checkbox>
</CheckboxGroup>
</div>
3. 기능 로직 만들기
내가 하고 만들고 싶은 기능
1. '전체동의' 체크박스가 활성화될때, 나머지 자식 4개의 체크박스도 활성화가 될 것
2. 반대로 '전체동의'가 체크가 비활성화 될때, 나머지 자식 4개의 체크박스도 체크가 비활성화 될 것
3. 나머지 자식 4개만이 체크 활성화가 될때, 부모인 '전체동의'도 함께 체크 활성화가 될 것
4. 3번의 경우에서 나머지 자식이 3개 이하로 체크 활성화가 될때 활성화되어있던 '전체동의'도 자동으로 비활성화 될것
로직 방향
부모는 boolean 타입의 값을 갖는 상태를 가지게 하고, 자식은 string[] 타입을 갖는 상태를 가지게 한다.
(1) 자식 상태가 가질 수 있는 경우의 수에 대한 로직 짜기
(2) 부모 상태가 가질 수 있는 경우의 수에 대한 로직 짜기
(1)-1 : value는 checkItems, onValueChanges는 setCheckItems. 따라서 체크버튼이 활성화 될때마다 setCheckItems에 value=
one"... 가 인자로 들어가지면서 checkItems의 상태가 변한다. 그리고 배열의 형태로 들어가고, 이 상태가 변할때마다 함수 컴포넌트는 다시 리랜더링이 된다.
<CheckboxGroup label="" color="success" value={checkItems} onValueChange={setCheckItems}>
<Checkbox value="one" className="text-[0.875rem]">(필수) 만 14세 이상입니다.
</Checkbox>
<Checkbox value="two">(필수) 이용약관에 동의합니다.</Checkbox>
<Checkbox value="three">(필수) 개인정보의 수집 및 이용에 동의합니다.</Checkbox>
<Checkbox value="four">(필수) 개인정보의 제3자가 제공에 동의합니다.</Checkbox>
</CheckboxGroup>
(1)-2 : 조건을 걸어주자. checkItems의 길이가 4이면(총 4개의 자식이 모두 한번씩 눌려져서 활성화가 된 상태라는 것), 부모 컴포넌트가 가지고 있는 value의 값을 true로 바꿔주기. 하지만 checkItems의 길이가 4보다 작을때는 부모컴포넌트 value를 false로 바꿔주기.
(2)-1 : 부모컴포넌트는 isSelectedAll 이라는 boolean 타입의 상태값을 가지는 상태를 가지고 있다. NextUI가 내장으로 가지고 있는 isSelected 속성에 isSelectedAll를 넣어주고, onValueChange에는 이벤트함수를 넣어준다.
(2)-2 : 이벤트 함수 로직짜기. handleCheckAll 이라는 함수는, 체크박스를 클릭하면서 data라는 인자를 갖는데 이 data를 찍어보면 처음엔 true만 계속 나온다. 이 상태에서 내가 원하는 방향은, isSelectedAll의 상태값이 true인 상태에서 체크박스를 누르면 false가, flase->true 즉 토글상태가 되게 만들어야 한다. 따라서, setIsSelectedAll 안에 함수 형태로 인자를 넣어준다. 그런다음 이제 data를 콘솔을 찍어보면, 토글 형식으로 data가 번갈아가면서 나온다.
이렇게 연결이 되었으면, 조건문만 잘 넣어주면 된다.
data가 참이면(체크박스가 활성화되어있다면) setCheckItems에 4개의 value가 모두 들어있는 배열을 넣어주고, false인 경우에는 빈배열을 넣어주기.
'use client';
import { Button, Checkbox, CheckboxGroup } from '@nextui-org/react';
import React, { Fragment, useEffect, useState } from 'react';
function Agreement() {
const [checkItems, setCheckItems] = useState<string[]>([]);
const [isSelectedAll, setIsSelectedAll] = useState<boolean>(false);
useEffect(() => {
if (checkItems?.length === 4) {
setIsSelectedAll(true);
} else if (checkItems?.length < 4) {
setIsSelectedAll(false);
}
}, [checkItems]);
const handleCheckAll = (data:boolean) => {
setIsSelectedAll((prev) => !prev);//
console.log('data:', data);
if (data) {
setCheckItems(['one', 'two', 'three', 'four']);
} else {
setCheckItems([]);
}
};
console.log('checkItems', checkItems);
console.log('register', register);
return (
<>
<div className="flex-col w-[360px] h-[160px] mt-6 ">
<Checkbox isSelected={isSelectedAll} onValueChange={handleCheckAll}>
아래 항목에 전부 동의합니다.
</Checkbox>
<CheckboxGroup label="" color="success" value={checkItems} onValueChange={setCheckItems}>
<Checkbox value="one" className="text-[0.875rem]">
(필수) 만 14세 이상입니다.
</Checkbox>
<Checkbox value="two">(필수) 이용약관에 동의합니다.</Checkbox>
<Checkbox value="three">(필수) 개인정보의 수집 및 이용에 동의합니다.</Checkbox>
<Checkbox value="four">(필수) 개인정보의 제3자가 제공에 동의합니다.</Checkbox>
</CheckboxGroup>
</div>
</>
);
}
export default Agreement;
4. 전체코드
'use client'; import { registerState } from '@/recoil/register'; import { Button, Checkbox, CheckboxGroup } from '@nextui-org/react'; import { addMonths, format } from 'date-fns'; import { useRouter } from 'next/navigation'; import React, { Fragment, useEffect, useState } from 'react'; import { useRecoilState } from 'recoil'; import useAlertModal from '@/components/common/modal/AlertModal'; import { id } from 'date-fns/locale'; function Agreement() { const [checkItems, setCheckItems] = useState<string[]>([]); const [isSelectedAll, setIsSelectedAll] = useState<boolean>(false); const router = useRouter(); const [register, setRegister] = useRecoilState(registerState); const currentDate = new Date(); const { openModal, AlertModal } = useAlertModal(); const period = addMonths(currentDate, 3); const dateFormat = 'yyyy-MM-dd HH:mm:ss XXX'; const maxCheckItems = 4; const handleNextBtn = () => { // 모든 체크박스가 선택된 경우를 확인 if (checkItems.length > 3) { setRegister((prevValue) => ({ ...prevValue, information_use_period: format(period, dateFormat), information_agreement: true })); router.push('#name'); } else { openModal('모든 체크박스를 선택해주세요.'); } }; useEffect(() => { if (checkItems?.length === 4) { setIsSelectedAll(true); } else if (checkItems?.length < 4) { setIsSelectedAll(false); } }, [checkItems]); const handleCheckAll = (data: boolean) => { setIsSelectedAll((prev) => !prev); // console.log('data:', data); if (data) { setCheckItems(['one', 'two', 'three', 'four']); } else { setCheckItems([]); } }; // console.log('checkItems', checkItems); // console.log('register', register); return ( <> <div className="flex-col align-center justify-center min-h-[calc(100dvh-12rem)] "> <div className="flex-col align-center"> <h1 className="text-[1.375rem] font-semibold text-black"> 서비스 가입을 위해 <br /> 이용약관에 동의해주세요. </h1> </div> <div className="flex-col w-[360px] h-[160px] mt-6 "> <Checkbox radius="full" color="success" isSelected={isSelectedAll} onValueChange={handleCheckAll}> 아래 항목에 전부 동의합니다. </Checkbox> <CheckboxGroup label="" color="success" value={checkItems} onValueChange={setCheckItems}> <Checkbox radius="full" value="one" className="text-[0.875rem]"> (필수) 만 14세 이상입니다. </Checkbox> <Checkbox radius="full" value="two"> (필수) 이용약관에 동의합니다. </Checkbox> <Checkbox radius="full" value="three"> (필수) 개인정보의 수집 및 이용에 동의합니다. </Checkbox> <Checkbox radius="full" value="four"> (필수) 개인정보의 제3자가 제공에 동의합니다. </Checkbox> </CheckboxGroup> </div> </div> <Button className={`w-full font-semibold bg-customYellow text-black rounded-3xl cursor-pointer mb-10 ${ checkItems.length === maxCheckItems ? 'bg-customGreen' : 'bg-customYellow' }`} onClick={handleNextBtn} > NEXT </Button> {AlertModal()} </> ); } export default Agreement;
고민해야할 점
useEffect dependency array에 무엇을 넣어줘야 할까?
-> 자식 컴포넌트의 value 를 넣어줘야 한다.
왜? 부모의 요소보다 자식의 요소가 많기때문에, 경우의 수가 더 많아진다. 부모요소가 딱 활성화 되고 안되고의 두가지 경우의 수로 떨어지기때문에 ...
'부트캠프 개발일지 2023-2024 > Bootcamp 생활기록' 카테고리의 다른 글
[16주차] 최종프로젝트 : postgresql 트리거 만들기 (0) | 2024.01.17 |
---|---|
[16주차] 최종프로젝트 : 실시간 알림 기능 - 알림시간 라이브러리 date-fns (0) | 2024.01.17 |
[15주차] 최종프로젝트 : Trigger 등록, SQL Editor, Supabase Auth, Schema Visualizer (0) | 2024.01.11 |
[15주차] 최종 프로젝트 : Realtime-Postgres Changes 이용하여 알림기능만들기(1) 기능테스트 (1) | 2024.01.11 |
[15주차] next.js와 App router 에 대한 이해 : 최적화, type import 컨벤션, Link, StaticSite Generation (1) | 2024.01.10 |