[React] Custom Select Box

박세화·2023년 7월 25일

React JS

목록 보기
14/22

기상 시간을 선택하면 예상 수면 시간을 계산해주는 기능을 구현하는 중에, 셀렉트 박스를 만들어야 했는데 라이브러리를 쓰면서는 디자인 시안과 완전히 똑같게 구현할 수가 없을 것 같아 걍 직접 만들었다.

import { useState, useContext, useEffect, useRef } from "react";

import {
  DispatchContext,
  WakeUpTimeContext,
} from "../contexts/wakeupTimeReducer.context";
import { hours, minutes } from "../utils/select-options";
import { ReactComponent as Expand } from "../svg/Expand_Down.svg";

import {
  ContentsBox,
  SelectArea,
  SelectBox,
  SelectOptions,
  Wrapper,
  Option,
  ButtonBox,
  MDbox,
} from "../styles/wakeupTime.component.styles";

const WakeupTime = () => {
  const hourSelectRef = useRef() as React.MutableRefObject<HTMLLIElement>;
  const minuteSelectRef = useRef() as React.MutableRefObject<HTMLLIElement>;

  const { hour, minute, md } = useContext(WakeUpTimeContext);
  const dispatch = useContext(DispatchContext);
  if (!dispatch) throw new Error("dispatch is null");

  const [currentHour, setCurrentHour] = useState(`${hour}`);
  const [currentMinute, setCurrentMinute] = useState(
    `${minute.toString().padStart(2, "0")}`
  );

  const [selected, setSelected] = useState({
    selectHour: hour,
    selectMinute: minute,
    selectMd: md,
  });

  useEffect(() => {
    dispatch({ type: "update", payload: selected });
  }, [selected]);

  const [showHours, setShowHours] = useState(false);
  const [showMinutes, setShowMinutes] = useState(false);

  const handleOnClick = (e: any) => {
    const { innerText, id } = e.target;
    if (id === "hour") {
      setCurrentHour(innerText);
      setSelected({ ...selected, selectHour: innerText });
    } else {
      setCurrentMinute(innerText);
      setSelected({ ...selected, selectMinute: innerText });
    }
    setShowHours(false);
    setShowMinutes(false);
  };

...

  let [active, setActive] = useState(true);
  if (md === "AM") {
    active = true;
  } else {
    active = false;
  }

  return (
    <ContentsBox>
      <SelectArea>
        <SelectBox ref={hourSelectRef}>
          <Wrapper onClick={() => setShowHours((prev) => !prev)}>
            <label>{currentHour}</label>
            <Expand />
          </Wrapper>
          <SelectOptions show={showHours} height="12.3rem" >
            {hours.map((item: number) => (
              <Option
                value={item}
                key={item}
                onClick={handleOnClick}
                id="hour"
              >
                {`${item}`}
              </Option>
            ))}
          </SelectOptions>
        </SelectBox>

        <SelectBox ref={minuteSelectRef}>
          <Wrapper onClick={() => setShowMinutes((prev) => !prev)}>
            <label>{currentMinute}</label>
            <Expand />
          </Wrapper>
          <SelectOptions show={showMinutes}>
            {minutes.map((item: number) => (
              <Option
                value={item}
                key={item}
                onClick={handleOnClick}
                id="minute"
              >
                {`${item.toString().padStart(2, "0")}`}
              </Option>
            ))}
          </SelectOptions>
        </SelectBox>
      </SelectArea>

      <ButtonBox>
        <MDbox
          active={active}
          className="am"
          onClick={() => {
            setSelected({ ...selected, selectMd: "AM" });
            setActive((prev) => !prev);
          }}
        >
          AM
        </MDbox>
        <MDbox
          active={!active}
          className="pm"
          onClick={() => {
            setSelected({ ...selected, selectMd: "PM" });
            setActive((prev) => !prev);
          }}
        >
          PM
        </MDbox>
      </ButtonBox>
    </ContentsBox>
  );
};

export default WakeupTime;
  • 시간,분 각각의 셀렉트 박스가 필요해서 하나의 컴포넌트로 만들고 불러와서 렌더링 하고 싶었으나 Context 와 코드가 충돌해서 그냥 중복코드로 작성했다.

  • SelectBox 내부는 선택된 부분을 보여주는 Wrapper 와 드롭다운 옵션 박스인 SelectOptions로 나뉜다.

  • Wrapper 부분을 클릭하면 드롭다운이 열려야 하기 때문에 onClick함수를 넣었다.

  • SelectOption 안에는 시간과 분 배열이 맵핑되어 각각의 옵션으로 나열된다. 특정 옵션을 선택했을 때 Wrapper 부분의 현재 선택값이 바뀌어야 하기 때문에 onClick 함수가 들어있다.

  • 0분을 00분으로 표시하기 위해 padStart 메소드를 추가했다.

  • AM, PM 버튼은 선택에 따라 색이 변화하며, useState로 불리언 상태를 관리해주었다.


👀 후에 중복되는 코드 최대한 없애며 리팩토링 예정

0개의 댓글