Web Frontend

React Checkbox 구현하기

이타심 2022. 7. 5. 00:19
728x90
Checkbox
오늘은 React로 checkbox를 구현하여 상태관리 하는 방법에 대해 공부해 볼 것이다. 

 

# 목표

전체 선택이 가능한 Checkbox와 각 리스트를 구성하는 element Checkbox를 만들어 컴포넌트 상태관리가 가능해야 한다.

 

 

# 구성 및 설계

- Total Component와 각 List Component를 생성한다. 이때 List는 map 함수를 사용한다.

- 반복문 사용시, 일반 배열이 아닌 Set을 사용하여 중복 없이 저장한다.

- 배열을 생성하고, useState를 통해 불변성 유지하며 상태를 관리한다.

- 각 Component에 Checked와 onChange props를 선언하여 on/off 기능을 구현한다.

- 전체 선택과 하나 선택 연동

 

# 전체적인 UI 틀 구성

checkbox는 HTML에서 제공하는 input 태그에 타입을 설정하여 구현할 수 있다.

<input type="checkbox"/>

다음과 같이 코드를 작성하면, 사각형 모양의 checkbox가 하나 생성되어 사용이 가능하다. 해당 코드를 가지고, 10개의 List Component와 하나의 total Component를 구성해 볼 것이다.

const TotalComponent = () =>{
	const mapList = [...Array(10).keys()]; 
    // Array(10)으로 10개의 공간 생성, ex) 0: undefined 
  	
    /* List Component */
    const List = (ele) => {
    	return(
          <input type="checkbox"/>
        );
    }
    
    return(
    	<>
         <header>
          <input type="checkbox"/>
	<header/>
            
         <ul className="flex flex-col gap-[8px]">
          {mapList.map((e,idx)=>(
            <List key={idx} id={e} />
          )}
         <ul/>
       </>
    );
};

export default TotalComponent;

다음과 같이 코드를 구성하게 되면, header속에 있는 하나의 전체 선택과 10개의 세부 선택 checkbox가 생성된다.

 

 

# Checkbox 상태관리

<각 List 선택>

앞서 전체적인 UI를 구성하였으니 이제 기능을 구현하여 상태관리를 시작해야 한다. checkbox를 상태관리 하는 것은, 먼저 check 여부에 대한 변수를 선언하여, 해당 변수가 true이면 체크되고, false이면 체크 해제되는 로직을 가지고 시작한다. 하지만 우리가 구현을 해야하는 List는 10개의 check여부를 판단해야 하기 때문에, List Checkbox 선택에 따른 Set 배열에 클릭한 checkbox의 인덱스를 저장하는 방식을 사용하였다.

const [checkedItems, setCheckedItems] = useState(new Set());

위와 같은 코드를 선언하여 선택한 List Checkbox를 담을 수 있는 배열을 만들고,

const checkedItemHandler = (id, isChecked) => {
  if (isChecked) {
    checkedItems.add(id);
    setCheckedItems(checkedItems);
  } else if (!isChecked && checkedItems.has(id)) {
    checkedItems.delete(id);
    setCheckedItems(checkedItems);
  }
};

Handler를 사용하여 해당 Set에 저장해준다. 

const TotalComponent = () =>{
	const mapList = [...Array(10).keys()]; // Array(10)으로 10개의 공간 생성, ex) 0: undefined 
  	
    /* List Component */
    const List = (ele) => {
     	const [bChecked, setChecked] = useState(false);
        const checkedHandler = ({ target }) => {
          //List onchange 호출 컴포넌트, 체크여부 판단해서 적용하고, 전달
          setChecked(!bChecked);
          checkedItemHandler(Number(target.id), target.checked);
        };

    	return(
         <input type="checkbox" checked={bChecked} onChange={(e) => checkHandler(e)} />;
        );
    }
    
    /* List Handler */
    const checkedItemHandler = (id, isChecked) => {
      if (isChecked) {
        checkedItems.add(id);
        setCheckedItems(checkedItems);
      } else if (!isChecked && checkedItems.has(id)) {
        checkedItems.delete(id);
        setCheckedItems(checkedItems);
      }
    };
    
    return(
    	<>
         <header>
          <input type="checkbox"/>
	<header/>
            
         <ul className="flex flex-col gap-[8px]">
          {mapList.map((e,idx)=>(
            <List key={idx} id={e} />
          )}
         <ul/>
       </>
    );
};

export default TotalComponent;

전체 코드를 보며 정리하면, 전체 선택과 각 List가 존재하고, 이때 각 List는 반복문으로 생성된다. 10개의 List는 bChecked state에 의해 체크 여부를 유무를 변경할 수 있고, 클릭했을 때 checkHandler에서 해당 값이 !bChecked를 통해 toggle된다. 이렇게 선택한 n번째 List component에 n번째 bChecked가 변경됨으로 n 번째 checkbox는 체크 설정/해제가 가능해진다. 체크 설정 이후에는 checkedItemHandler로 이동하여 배열에 해당 n 번째 index를 저장한다.

 

따라서 checkbox 클릭 >> checkHandler에서 bChecked toggle >> checkbox 체크변경 >> checkedItemHandler에서 클릭한 값 배열에 저장 순으로 진행된다.

 

<전체 선택>

전체 선택의 경우, List에서 실시했던 check여부 판단을 위한  state 생성 및 사용은 동일하지만, 일부 구현 방법에서 차이가 있다.

const [isAllChecked, setIsAllChecked] = useState(false);

const allCheckedHandler = (e) => {
  if (!isAllchecked) {
    setCheckedItems(new Set(mapList.map((id) => id))); //전체 선택 눌렀을 때, set에 모든 id 값 넣는다.
    setIsAllChecked(true);
  } else {
    setCheckedItems(new Set()); // 전체선택 해제시 set 비우고 적용
    setIsAllChecked(false);
  }
};

전체 선택의 경우에도, 앞선 bChecked와 같이 isAllchecked를 활용하여 체크 유무를 판단하고, 동일하게 Handler를 통해 배열에 해당 값을 저장하여 클릭을 완료한다. 현재까진 동일하지만, 전체 선택에서는 추가적으로 구현해야 할 기능이 있다. 그것은 전체 선택 클릭시, 본인 checkbox toggle뿐만 아니라, 전체 선택과 동일하게 List checkbox도 toggle되어야 한다는 것이다. 

 

따라서 변경되는 isAllchecked에 따라 List Component에 변화를 주면 된다.

  /* component */
  const ListComponent = (ele) => {
    const [bChecked, setChecked] = useState(false);
    const checkedHandler = ({ target }) => {
      //List onchange 호출 컴포넌트, 체크여부 판단해서 적용하고, 전달
      setChecked(!bChecked);
      checkedItemHandler(Number(target.id), target.checked);
    };
 
   const allCheckedHandler = () => setChecked(isAllchecked);
   
   useEffect(() => {
      allCheckedHandler();
    }, [isAllchecked]);
  
   // 개별 checkList 구현을 위해 반복해서 출력 예정 
   return (
      <input
        type="checkbox"
        id={ele.id}
        checked={bChecked}
        onChange={checkedHandler}
      />
    );
  };

해당 코드는 앞선 List Component 중심부에 코드를 삽입한 결과이다. useEffect를 사용하여, 외부에서 전체 선택 버튼이 변경 된 경우 allCheckHandler를 호출하여 List도 수정시켜 준다. 해당 과정은 특정 선택한 List가 아닌, 전체적으로 일괄 변경되기 때문에 전체 선택에 따른 List 상태관리가 가능하다.

 

<전체코드>

import React, { useEffect, useState } from "react";

const TotalComponent = () => {
  /* 필요 state */
  const mapList = [...Array(10).keys()]; // Array(10)으로 10개의 공간 생성, ex) 0: undefined 여기서 .keys()로 values에 숫자 삽입
  const [checkedItems, setCheckedItems] = useState(new Set()); //빈 set 생성
  const [isAllchecked, setIsAllChecked] = useState(false);

  /* component */
  const ListComponent = (ele) => {
    const [bChecked, setChecked] = useState(false);
    
    const checkedHandler = ({ target }) => {
      //List onchange 호출 컴포넌트, 체크여부 판단해서 적용하고, 전달
      setChecked(!bChecked);
      checkedItemHandler(Number(target.id), target.checked);
    };
    const allCheckedHandler = () => setChecked(isAllchecked);
   
   useEffect(() => {
      allCheckedHandler();
    }, [isAllchecked]);
    
    // 개별 checkList 구현을 위해 반복해서 출력 예정
    return (
      <input
        type="checkbox"
        id={ele.id}
        checked={bChecked}
        onChange={checkedHandler}
      />
    );
  };

  /* 상태관리 component */
  const checkedItemHandler = (id, isChecked) => {
    //Issue 컴포넌트 props로 전달하여 checkbox 클릭 시 실행
    if (isChecked) {
      // 체크되면 id add
      checkedItems.add(id);
      setCheckedItems(checkedItems);
    } else if (!isChecked && checkedItems.has(id)) {
      // check가 해제되고 setd에 id가 있으면 delete
      checkedItems.delete(id);
      setCheckedItems(checkedItems);
    }
    console.log(checkedItems);
  };

  const allCheckedHandler = (e) => {
    console.log(isAllchecked, "<<<");
    if (!isAllchecked) {
      setCheckedItems(new Set(mapList.map((id) => id))); //전체 선택 눌렀을 때, set에 모든 id 값 넣는다.
      setIsAllChecked(true);
    } else {
      // checkedItems.clear();
      // setCheckedItems(setCheckedItems);
      setCheckedItems(new Set()); // 전체선택 해제시 set 비우고 적용
      setIsAllChecked(false);
    }
    console.log(checkedItems);
  };

  return (
    <>
       <div className="flex-1 overflow-y-auto justify-center gap-[12px]">
         <header className="flex justify-center py-10">
          <input
            type="checkbox"
            checked={isAllchecked}
            onChange={allCheckedHandler}
          />
        </header>

        <ul className="flex flex-col gap-[8px]">
          {mapList.map((e, idx) => (
            <ListComponent key={idx} id={e} />
          ))}
        </ul>
      </div>
    </>
  );
};

export default TotalComponent;

 

728x90

'Web Frontend' 카테고리의 다른 글

React Hooks  (0) 2022.07.14
React Lifecycle method  (0) 2022.07.12
React ForEach, Map  (0) 2022.07.12
React Component  (0) 2022.07.07
React Props, State  (2) 2022.07.06