스터디 관리 프로젝트 - Modal

Seuling·2023년 3월 7일
0
post-thumbnail

체크 버튼을 클릭시, 모달이 나온다.

TODO - 체크 버튼 클릭

//TodoContent.tsx
export default function TodoContent({ content, id, fetchData }: Props) {

  const [isOpenModal, setIsOpenModal] = useState(false);
  const clickModal = () => {
    setIsOpenModal(true);
  };
  const closeModal = () => {
    setIsOpenModal(false);
  };
 
  return (
    ~~~
     <div className="todo-content-wrapper">
          <button
            onClick={() => {
              clickModal();
            }}
          >
            <Icon.CheckCircle size={18} color="white" />
          </button>
     </div>

  ~~~
  {isOpenModal && (
        <Modal
          variant="certification"
          onSubmit={onSubmit}
          closeModal={closeModal}
        />
      )}
  )
}
//Modal.tsx
import React, { ChangeEvent, FormEvent, useRef, useState } from "react";
import Button from "./Button";
import Text from "./Text";
import * as Icon from "react-feather";
import { updateTodo } from "../../services/api/todo";

type Variant = "code" | "certification";
interface Props {
  variant?: Variant;
  closeModal: () => void;
  onSubmit: any;
}

interface UpdateParams {
  key: "authenticationMethod" | "authenticationContent";
  value: string;
}

export default function Modal({ variant, closeModal, onSubmit }: Props) {
  const inputRef = useRef(null);

  const [newDone, setNewDone] = useState<TodoParam>({
    complitedAt: new Date(),
    authenticationMethod: "",
    authenticationContent: "",
  });

  const [isShowImage, SetIsShowImage] = useState(false);

  const update = (params: UpdateParams[]) => {
    const _newDone = { ...newDone };
    params.forEach((p) => {
      _newDone[p.key] = p.value;
    });
    setNewDone(_newDone);
  };

  return (
      <div className="background" onClick={closeModal}>
        <div className="modal-container" onClick={(e) => e.stopPropagation()}>
          {variant === "code" ? (
            <div className="variant-code">
              <Text type="title">입장코드를 입력해주세요</Text>
              <input placeholder="e.g. 1234567" />
            </div>
          ) : (
            <form className="variant-certification">
              <Text type="title">공부내용 인증</Text>
              <Text type="title" size="sm">
                블로그 인증
              </Text>
              <input
                onChange={(e) => {
                  update([
                    {
                      key: "authenticationContent",
                      value: e.currentTarget.value,
                    },
                    {
                      key: "authenticationMethod",
                      value: "link",
                    },
                  ]);
                }}
                placeholder="블로그 링크를 입력해주세요"
              />
              <Text type="title" size="sm">
                스크린샷 인증
              </Text>
              {isShowImage ? (
                <img
                  src={newDone.authenticationContent}
                  alt={newDone.authenticationContent}
                />
              ) : (
                <>
                  <div
                    className="input-file-box"
                    onClick={() => {
                      (inputRef.current as any).click();
                    }}
                  >
                    <Icon.UploadCloud size={18} color="#828fa3" />
                    <Text size="md" color="gray" style={{ marginLeft: "5px" }}>
                      파일 업로드
                    </Text>
                  </div>
                  <input
                    className="hidden"
                    ref={inputRef}
                    type="file"
                    style={{ visibility: "hidden" }}
                    name={"fileName"}
                    onChange={(e) => {
                      e.target.files &&
                        update([
                          { key: "authenticationMethod", value: "image" },
                          {
                            key: "authenticationContent",
                            value: e.target.files[0].name,
                          },
                        ]);

                      SetIsShowImage(true);
                    }}
                  />
                </>
              )}
            </form>
          )}
          <div className="button-wrapper">
            <Button onClick={closeModal} size="sm" variant="outlined">
              취소
            </Button>
            <Button onClick={(e: React.MouseEvent<HTMLButtonElement>) => onSubmit(e, newDone)} size="sm">
              확인
            </Button>
          </div>
        </div>
      </div>
  );
}

  • 먼저 모달컴포넌트는 type Variant = "code" | "certification" 에 따라 2가지 다른 모달을 보여준다.
    현재 하고 있는 형태는 인증과 관련된 certification Modal 부분을 구현할 것이다.

  • newDone 의 상태를 보면 객체를 가지고있다. complitedAt, authenticationMethod, authenticationContent 의 상태를 동시에 관리하기 위해서!

  • update 함수를 보자면 먼저 newDone의 값을 복사해오고, param을 key, value의 객체로 받아오는데, 이 받아온 객체를 forEach로 순회하며 복사하여 만든 _newDone의 key와 value에 해당하는것을 매핑해주고, setNewDone으로 newDone을 업데이트 해주게된다.

  • _newDone으로 한번 더 복사해서 사용한 이유는 ? 🤔 _newDone으로 update함수 내에서만 사용될 변수를 만들어줘서 newDone의 값을 복사해와서 key와 value에 따라서 _newDone을 만들어준뒤, setNewDone에 _newDone을 넣어 newDone의 상태를 변경해주었다.
    newDone은 state이기에 newDone 자체의 값을 변경해 줄 때에는 setState로만 해줘야하니까!

  • spread 연산자를 이용하면 깊은복사일까 얕은복사일까

  • currentTarget vs target ?

    • currentTarget: 이벤트가 발생한 요소의 부모 요소
    • target: 이벤트가 발생한 요소

    예를 들어, 버튼을 클릭하면 currentTarget은 버튼의 부모 요소인 div이고 target은 버튼 자체입니다.

    • target 과 currentTarget을 섞어 사용한 이유는... input의 값으로 file 데이터를 가져와야하는데 currentTarget에는 file이 없기에 targer을 사용했다.
  • background에 onClick으로 closeModal을 해주었는데, 모달창내에 input이나 다른 요소를 클릭하더라도 closeModal이 적용되는 이벤트 버블링 현상이 발생하였다. -> 좀 더 자세히 알아보기!!!

  • 막기위해 e.stopPropagation()을 사용

CSS

.background {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 0;
  @include flexbox();
}

.modal-container {
  @include column-flexbox();
  gap: 20px;
  background-color: $grayDark;
  padding: 20px;
  width: 100%;
  max-width: 440px;

  .variant-certification {
    @include column-flexbox(start, start);
    width: 100%;
    gap: 20px;
    input {
      border: 1px solid $gray;
    }

    .input-file-box {
      @include flexbox();
      background-color: $background;
      width: 100%;
      padding: 0 16px;
      height: 40px;
      border: 1px solid $background;
      border-radius: 4px;
      box-sizing: border-box;
      cursor: pointer;
      &:hover {
        opacity: 0.8;
      }
    }

    .hidden {
      position: fixed;
    }
  }

  .button-wrapper {
    @include flexbox();
    gap: 10px;
    width: 100%;
  }
}
  • background 라는 클래스를 만들어서 최상위 div에 background로 background-color의 색을 흐리게 만들어주었다.

input type =file 일 경우 custom

profile
프론트엔드 개발자 항상 뭘 하고있는 슬링

0개의 댓글