본문 바로가기
부트캠프 개발일지 2023-2024/Bootcamp 생활기록

[16주차] 최종프로젝트 : 리액트 라이프사이클, 체크박스(all포함), NextUI

by whereanna00 2024. 1. 17.

리액트 라이프사이클, 체크박스(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 는 아래와 같다.

 

개인정보동의 checkbox

 

 

 

따라서 골격을 먼저 만들어주면 아래와 같다.

 

        <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 를 넣어줘야 한다. 

왜? 부모의 요소보다 자식의 요소가 많기때문에, 경우의 수가 더 많아진다. 부모요소가 딱 활성화 되고 안되고의 두가지 경우의 수로 떨어지기때문에 ...

 

 

 

 

React Tutorial – How to Work with Multiple Checkboxes

Handling multiple checkboxes in React is completely different from how you use regular HTML checkboxes. So in this article, we'll see how to work with multiple checkboxes in React. You will learn: * How to use a checkbox as a Controlled Input in React * Ho

www.freecodecamp.org

 

728x90
반응형