[실전 프로젝트] 드롭다운 기능 만들기
본문 바로가기
항해 중/8-13주차 실전 프로젝트

[실전 프로젝트] 드롭다운 기능 만들기

by 은돌1113 2022. 1. 2.

아래 사이트를 참고하여 드롭다운 기능을 만들었습니다.

https://velog.io/@tunakim/select-%ED%83%9C%EA%B7%B8-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EA%B3%A0-%EB%93%9C%EB%A1%AD%EB%8B%A4%EC%9A%B4-%EA%B5%AC%ED%98%84-ft.-React

 

select 태그 사용하지 않고 드롭다운 구현 (ft. React)

select 태그의 디자인 어려움 문제로 select를 사용하지 않고 css(Styled-Components)를 이용해 드롭다운을 구현해봤습니다.드롭다운 기본 기능.드롭다운이 펼쳐졌을 때 css 처리 ( 밑의 DOM 요소를 덮는지,

velog.io

 

구현 코드 (초기 코드)

더보기
더보기
// src/components/DropDown.js

import React from "react";
import styled from "styled-components";

const Dropdown = (props) => {
  const [isActive, setIsActive] = React.useState(false);
  const [item, setItem] = React.useState(null);

  const onActiveToggle = () => {
    setIsActive(true);
  };

  const onSelectItem = (name) => {
    setItem(name);
    props.state(name);
    setIsActive(false);
  };

  // 알림 비활성화일 때
  if (props.state === "disabled") {
    return (
      <DisabledDropDownContainer>
        <DropdownBody color="gray">
          <ItemName>{`${props.title}${props.condition}`}</ItemName>
        </DropdownBody>
      </DisabledDropDownContainer>
    );
  }

  // 알림 활성화일 때
  return (
    <DropdownContainer>
      <DropdownBody onClick={onActiveToggle}>
        {item ? (
          <>
            <ItemName>{`${item}${props.condition}`}</ItemName>
          </>
        ) : (
          <>
            <DropdownSelect>{`${props.title}${props.condition}`}</DropdownSelect>
          </>
        )}
      </DropdownBody>
      <DropdownMenu id="type2" isActive={isActive}>
        {props.dropdownItems &&
          props.dropdownItems.map((dropdowmItem) => (
            <DropdownItemContainer
              key={dropdowmItem}
              onClick={() => {
                onSelectItem(dropdowmItem);
              }}
            >
              <p>{`${dropdowmItem}${props.condition}`}</p>
            </DropdownItemContainer>
          ))}
      </DropdownMenu>
    </DropdownContainer>
  );
};

const DropdownContainer = styled.div`
  width: 100px;
  margin: auto;
  text-align: center;
  border: 1px solid gray;

  &:hover {
    cursor: pointer;
    border: 3px solid #fbc037;
    border-radius: 10px;
  }
`;

const DisabledDropDownContainer = styled.div`
  width: 100px;
  margin: auto;
  text-align: center;
  border: 1px solid gray;
`;

const DropdownBody = styled.div`
  align-items: center;
  color: ${(props) => props.color && props.color};
`;

const DropdownSelect = styled.p`
  font-weight: bold;
`;

const DropdownMenu = styled.ul`
  display: ${(props) => (props.isActive ? `block` : `none`)};
  width: 60px;
  max-height: 200px;
  overflow: scroll;
  background-color: white;
  position: absolute;
  margin-top: -1px;
  margin-left: -3px;
  border: 3px solid #fbc037;
  border-radius: 10px;
`;

const DropdownItemContainer = styled.li`
  display: flex;
  justify-content: space-between;
  margin-left: -5px;

  &:last-child {
    border-bottom: none;
  }
`;

const ItemName = styled.p`
  font-weight: bold;
`;

export default Dropdown;
// src/pages/PushNoticationPop.js

  const checkItems = ["AM", "PM"];
  const hourItems = [
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    "10",
    "11",
    "12",
  ];
  const minutesItems = [
    "00",
    "05",
    "10",
    "15",
    "20",
    "25",
    "30",
    "35",
    "40",
    "45",
    "50",
    "55",
  ];
  
  -------------------------------------------------
  
<DropDown
condition={""}
title={"PM"}
dropdownItems={checkItems}
state={setDay}
></DropDown>
<DropDown
condition={"시"}
title={"12"}
dropdownItems={hourItems}
state={setHour}
></DropDown>
<DropDown
condition={"분"}
title={"00"}
dropdownItems={minutesItems}
state={setMinutes}
></DropDown>

 

문제 발생

 

1. 초기 코드에서 코드를 변형 시키는 과정에서 부모 컴포넌트(알림 팝업창) → 자식 컴포넌트(드롭다운 컴포넌트)에 부모 컴포넌트에 있는 state나, 데이터를 보내주는데 (아래 코드처럼 설정했습니다.)

<DropDown
  minutesActive={minutesActive}
  setDayActive={setDayActive}
  setHourActive={setHourActive}
  setMinutesActive={setMinutesActive}
  condition={"분"}
  title={"00"}
  minutesItems={minutesItems}
  state={setMinutes}
></DropDown>;

 

2. 부모 컴포넌트에서 넘어온 props의 state 변수 값을 자식 컴포넌트에서 state에 담아줬다. (모든 문제의 시작은 여기였다... 해결한 뒤 생각 해보니 왜 부모 컴포넌트에서 받은 걸 자식 컴포넌트에서 state에 담아서 이중 state를 만들 생각을 했는 지 도무지 모르겠다..)

  const [day, setDay] = React.useState(
    props.dayActive ? props.dayActive : false
  );
  const [hour, setHour] = React.useState(
    props.hourActive ? props.hourActive : false
  );
  const [minutes, setMinutes] = React.useState(
    props.minutesActive ? props.minutesActive : false
  );


3. useState를 사용 시 setState를 사용하여 state의 값을 변경 할 수 있고, 변경 시 컴포넌트가 렌더링 되어서 값이 반영 되는 걸로 알고 있었는데, 바로 반영이 되지 않아서 5시간을 헤맸다

 

4. 튜터님께 여쭤보고, 팀원들과 상의 해보고, 구글링 해본 결과 튜터님에게는 이런 답변이 왔다.

 

dayActive(부모 컴포넌트의 useState)의 (true/false)값을 참조하여 보여 줘야 되는 것 같은데 맞나요?

day(자식 컴포넌트의 useState, props.dayAction을 초기값으로 받음)를 참조하여 뷰를 보여주는 부분이 의아합니다.

 

이 글을 보고 머리가 띵 하고, 순간 얼어 버렸으며 머릿 속에 내가 지금까지 뭘 한거지???, ????????? 등등 온갖 생각이 스쳐 지나갔다. (빈말 아니고 진심이다.) 너무 부끄럽고 속상 했는데.. 튜터님이

 

하나에 매달리다보면 시야가 좁아질 수 밖에 없어요 (터널 비전)

문제가 잘 보이지 않을땐, 잠시 쉬고 다시 보면 문제가 보일 수 있답니다.

 

라고 해주셔서.. 너무 감사했고 빨리 끝내고 싶고, 잘하고 싶다는 욕심에 판단이 흐려졌구나.. 앞으로 안될 때는 잠시 쉬면서 해야겠다는 깨달음을 얻을 수 있었습니다.

 

5. 이중 state를 만들고 있었던 코드는 다 지우고, props로 받아온 state만 사용하도록 수정 했더니 그토록 원하던 드롭다운을 얻을 수 있었습니다.

더보기
더보기
// src/components/PushNoticationPop.js (부모 컴포넌트)

// 푸시 알림 팝업 페이지
import React from "react";
import Modal from "react-modal";
import Switch from "@mui/material/Switch";

import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
import { actionCreators as noticeActions } from "../redux/modules/notice";
import DropDown from "../elements/DropDown";

const PushNoticationPop = (props) => {
  const [modal, setModal] = React.useState(props.modal ? true : false); // 모달창
  const [notice, setNotice] = React.useState(true); // 알림 유무
  const [day, setDay] = React.useState("PM"); // 오전(true), 오후(false) 설정
  const [hour, setHour] = React.useState(12); // 시 설정
  const [minutes, setMinutes] = React.useState(0); // 분 설정

  const dispatch = useDispatch();
  const label = { inputProps: { "aria-label": "Switch demo" } };

  const [dayActive, setDayActive] = React.useState(false);
  const [hourActive, setHourActive] = React.useState(false);
  const [minutesActive, setMinutesActive] = React.useState(false);

  const dayItems = ["AM", "PM"];
  const hourItems = [
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    "10",
    "11",
    "12",
  ];
  const minutesItems = [
    "00",
    "05",
    "10",
    "15",
    "20",
    "25",
    "30",
    "35",
    "40",
    "45",
    "50",
    "55",
  ];

  const send = () => {
    if (!notice) {
      // 알림 안받는 경우 → 미들웨어에 기본값을 설정 해줘야 합니다.
      dispatch(noticeActions.noticePopDB(notice));
    } else {
      // 알림 받는 경우
      dispatch(noticeActions.noticePopDB(notice, day, hour, minutes));
    }

    localStorage.setItem("noticeSet", true);
    props.setNoticationModal(false);
  };

  return (
    <>
      <Modal
        isOpen={modal}
        ariaHideApp={false}
        style={{
          overlay: {
            position: "fixed",
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            backgroundColor: "rgba(15, 15, 15, 0)",
            zIndex: "1",
          },
          content: {
            position: "absolute",
            top: "60px",
            left: "35%",
            width: "30%",
            height: "80%",
            border: "1px solid #ccc",
            background: "#fff",
            overflow: "auto",
            WebkitOverflowScrolling: "touch",
            borderRadius: "4px",
            outline: "none",
            padding: "20px",
          },
        }}
      >
        <h1>매일 알림 받고 기록하기</h1>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          수면 기록 알림 받기 &nbsp;
          <Switch
            {...label}
            onClick={() => {
              setNotice(!notice);
              setDayActive(false);
              setHourActive(false);
              setMinutes(false);
            }}
            style={{ color: "#FBC037" }}
            color="default"
            defaultChecked
          />
        </div>
        <div>
          {notice ? (
            <>
              <div style={{ display: "flex", justifyContent: "space-evenly" }}>
                <DropDown
                  dayActive={dayActive}
                  setDayActive={setDayActive}
                  setHourActive={setHourActive}
                  setMinutesActive={setMinutesActive}
                  condition={""}
                  title={"PM"}
                  dayItems={dayItems}
                  state={setDay}
                ></DropDown>
                <DropDown
                  hourActive={hourActive}
                  setDayActive={setDayActive}
                  setHourActive={setHourActive}
                  setMinutesActive={setMinutesActive}
                  condition={"시"}
                  title={"12"}
                  hourItems={hourItems}
                  state={setHour}
                ></DropDown>
                <DropDown
                  minutesActive={minutesActive}
                  setDayActive={setDayActive}
                  setHourActive={setHourActive}
                  setMinutesActive={setMinutesActive}
                  condition={"분"}
                  title={"00"}
                  minutesItems={minutesItems}
                  state={setMinutes}
                ></DropDown>
              </div>
            </>
          ) : (
            <div style={{ display: "flex", justifyContent: "space-evenly" }}>
              <DropDown state="disabled" condition={""} title={"PM"}></DropDown>
              <DropDown
                state="disabled"
                condition={"시"}
                title={"12"}
              ></DropDown>
              <DropDown
                state="disabled"
                condition={"분"}
                title={"00"}
              ></DropDown>
            </div>
          )}

          <button onClick={send}>확인</button>
        </div>
      </Modal>
    </>
  );
};

export default PushNoticationPop;
// src/elements/DropDown.js (자식 컴포넌트)

import React from "react";
import styled from "styled-components";

const Dropdown = (props) => {
  const [item, setItem] = React.useState(null);

  const onActiveToggle = () => {
    if (props.condition === "") {
      dayChange();
    } else if (props.condition === "시") {
      hourChange();
    } else if (props.condition === "분") {
      minutesChange();
    }
  };

  const dayChange = () => {
    props.setDayActive(!props.dayActive);
    props.setHourActive(false);
    props.setMinutesActive(false);
  };

  const hourChange = () => {
    props.setDayActive(false);
    props.setHourActive(!props.hourActive);
    props.setMinutesActive(false);
  };

  const minutesChange = () => {
    props.setDayActive(false);
    props.setHourActive(false);
    props.setMinutesActive(!props.minutesActive);
  };

  const onSelectItem = (name) => {
    setItem(name);
    props.state(name);

    if (props.dayActive) {
      props.setDayActive(!props.dayActive);
    } else if (props.hourActive) {
      props.setHourActive(!props.hourActive);
    } else if (props.minutesActive) {
      props.setMinutesActive(!props.minutesActive);
    }
  };

  // 알림 비활성화일 때
  if (props.state === "disabled") {
    return (
      <DisabledDropDownContainer>
        <DropdownBody color="gray">
          <ItemName>{`${props.title}${props.condition}`}</ItemName>
        </DropdownBody>
      </DisabledDropDownContainer>
    );
  }

  // 알림 활성화일 때
  return (
    <DropdownContainer>
      <DropdownBody onClick={onActiveToggle}>
        {item ? (
          <>
            <ItemName>{`${item}${props.condition}`}</ItemName>
          </>
        ) : (
          <>
            <DropdownSelect>{`${props.title}${props.condition}`}</DropdownSelect>
          </>
        )}
      </DropdownBody>
      {props.dayActive ? (
        <DropdownMenu id="type2" isActive={props.dayActive}>
          {props.dayItems &&
            props.dayItems.map((item) => (
              <DropdownItemContainer
                key={item}
                onClick={() => {
                  onSelectItem(item);
                }}
              >
                <p>{`${item}${props.condition}`}</p>
              </DropdownItemContainer>
            ))}
        </DropdownMenu>
      ) : null}

      {props.hourActive ? (
        <DropdownMenu id="type2" isActive={props.hourActive}>
          {props.hourItems &&
            props.hourItems.map((item) => (
              <DropdownItemContainer
                key={item}
                onClick={() => {
                  onSelectItem(item);
                }}
              >
                <p>{`${item}${props.condition}`}</p>
              </DropdownItemContainer>
            ))}
        </DropdownMenu>
      ) : null}

      {props.minutesActive ? (
        <DropdownMenu id="type2" isActive={props.minutesActive}>
          {props.minutesItems &&
            props.minutesItems.map((item) => (
              <DropdownItemContainer
                key={item}
                onClick={() => {
                  onSelectItem(item);
                }}
              >
                <p>{`${item}${props.condition}`}</p>
              </DropdownItemContainer>
            ))}
        </DropdownMenu>
      ) : null}
    </DropdownContainer>
  );
};

const DropdownContainer = styled.div`
  width: 100px;
  margin: auto;
  text-align: center;
  border: 1px solid gray;

  /* &:hover {
    cursor: pointer;
    border: 3px solid #fbc037;
    border-radius: 10px;
  } */
`;

const DisabledDropDownContainer = styled.div`
  width: 100px;
  margin: auto;
  text-align: center;
  border: 1px solid gray;
`;

const DropdownBody = styled.div`
  align-items: center;
  color: ${(props) => props.color && props.color};
`;

const DropdownSelect = styled.p`
  font-weight: bold;
`;

const DropdownMenu = styled.ul`
  display: ${(props) => (props.isActive ? `block` : `none`)};
  width: 60px;
  max-height: 200px;
  overflow: scroll;
  background-color: white;
  position: absolute;
  margin-top: -1px;
  margin-left: -3px;
  border: 3px solid #fbc037;
  border-radius: 10px;
  overflow-x: hidden; // 가로 축 스크롤 감추기
`;

const DropdownItemContainer = styled.li`
  display: flex;
  justify-content: space-between;
  margin-left: -5px;

  &:last-child {
    border-bottom: none;
  }
`;

const ItemName = styled.p`
  font-weight: bold;
`;

export default Dropdown;

댓글