2024.04.12 TIL - 최종프로젝트 18일차 (반응형에 대한 연구-window.innerWidth, "resize", useEffect clean up함수, zustand활용 / mode에 따른 포인트 업데이트 / 공용 모달창 생성)

Innes·2024년 4월 12일
0

TIL(Today I Learned)

목록 보기
114/147

📝 오늘 한 일

✔️ about페이지 디자인 이슈 해결 (isDesktop, isLaptop 선언 오류 해결)
✔️ 카드 4장 isDesktop일땐 Justify-end 안하고싶은데 ㅠㅠ -> 해결
✔️ about페이지 laptop 디자인 마무리
(custom hook, zustand 생성)
✔️ 포인트 획득 로직 추가 (개인액션, 게시글 등록, 댓글 등록)
✔️ 빌드 (yarn build, yarn start)
✔️ 배포 (주소 나만 볼수있는거 <-> 공용 주소 따로 존재하는걸 알게됨)
✔️ 포인트 획득 로직추가 (개인액션 등록, 게시글 등록, 댓글 등록)
✔️ alert 전부다 모달창으로 리팩토링 ㄷㄷ 엄청 많았다 (아래의 x2배)


✨ 반응형 - 트러블 슈팅

  • 문제상황 : about페이지에서 화면이 줄어도 늘어도, 변해야 할 내용들이 그냥 1920 기준 픽셀 혹은 1020 기준 픽셀로 고정되어 있는 상황 발생

  • 원인 :
    const isDesktop = window.innerWidth >= 1920;
    이런식으로 선언해서는 화면 사이즈가 변했다는 사실을 바로바로 반영하지 못하기 때문

  • 해결 : useEffect로 화면크기 변경 있을 때마다 바로바로 캐치할 수 있도록 만들기

      1. window.addEventListener("resize", handleResize);
      • 브라우저 화면이 resize되는지 확인하는 eventListener 추가하기
      • "resize"는 브라우저가 기본적으로 갖고 있는 이벤트 중 하나이다.

      1. window.innerWidth를 set해주기
      • useState든, zustand든 화면 크기 상태를 관리해서 브라우저의 innerWidth를 set해준다.
      • 이 부분은 함수로 만들어놓고, addEventListener 생성 후에 함수를 바로 실행시킬 것임

      1. clean up 함수 생성
      • useEffect 안에서 addEventListener했으면 해당 useEffect 사용하는 컴포넌트마다 계속 eventListener가 쌓일 것.
      • 언마운트시 eventListener를 지울 수 있도록 useEffect 함수 마지막에 return문으로 clean up함수를 만든다.
  • 추가 내용

    • 사실 javascript 문법을 그대로 리액트에서 사용하는게 그다지 권장되는 사항은 아니지만, 리액트에서 윈도우 사이즈 실시간으로 재는 기능을 제공하는지 몰라서 js문법 사용하는 방식을 채택했다.
// page.tsx - about페이지

const AboutPage = () => {
  // custom hook - 현재 브라우저 화면의 사이즈 상태 가져오기
  const { isDesktop, isLaptop, isMobile } = useResponsive();
  //...생략
// responsiveStore.ts - zustand로 브라우저 화면사이즈, set함수 관리

import { create } from "zustand";

interface Responsive {
  isDesktop: boolean;
  isLaptop: boolean;
  isMobile: boolean;
  setWindowSize: (width: number) => void;
}

export const useResponsiveStore = create<Responsive>((set) => ({
  isDesktop: false,
  isLaptop: false,
  isMobile: false,
  setWindowSize: (width: number) => {
    set({
      isDesktop: width >= 1920,
      isLaptop: width >= 1020 && width < 1920,
      isMobile: width < 1020 && width >= 360,
    });
  },
}));
// index.ts - custom hook (useEffect로 화면사이즈 변동 파악, 화면별 boolean return)

import { useEffect } from "react";
import { useResponsiveStore } from "@/app/_store/responsiveStore";

export const useResponsive = () => {
  const setWindowSize = useResponsiveStore((state) => state.setWindowSize);

  useEffect(() => {
    // ⭐️ 화살표함수는 this 바인딩을 생략하기 때문에 function기본형 함수 써야함
    function handleResize() {
      console.log("리사이즈가 동작하고 있습니다.");

      // 여기에 set~~ 코드 삽입
      setWindowSize(window.innerWidth);
    }

    // window에 이벤트 등록 (browser가 기본적으로 갖고있는 이벤트 중 하나 "resize")
    // resize가 일어날때 handleResize 함수가 동작함
    // (리액트에서 윈도우 사이즈 실시간으로 재는 기능 제공하는지 몰라 js문법 사용)
    window.addEventListener("resize", handleResize);

    handleResize();

    // cleanup 함수
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, [setWindowSize]);

  // (기존에 zustand에서 가져와 쓰는 방식 예시)
  //   const isDesktop = useResponsiveStore((state)=>state.isDesktop);

  // (custom hook에서 return하는게 헷갈린다면 참고하도록 풀어써본 코드)
  //   const resultObj = useResponsiveStore(function(state) {
  //     let isDesktop = state.isDesktop;
  //     let isLaptop = state.isLaptop;
  //     let isMobile = state.isMobile;

  //     return {
  //         isDesktop: isDesktop,
  //         isLaptop: isLaptop,
  //         isMobile: isMobile
  //     }
  //   });

  const resultObj = useResponsiveStore((state) => {
    return {
      isDesktop: state.isDesktop,
      isLaptop: state.isLaptop,
      isMobile: state.isMobile,
    };
  });

  return resultObj;
};

mode에 따른 포인트 업데이트(supabase)

  • 예시 코드
// AddPostModal.tsx : _components/ community
// 컴포넌트에서 포인트 업데이트시 - mode도 함께 인자로 전달

// 🧡 { mode: "" } 이렇게 보내주지만, 인자로 받을때는 option:{ mode : "" }로 받는다.
 await updateUserPoint(loggedInUserUid, { mode: "addPost" });
// add-api.ts

// 2. point 업데이트 - mode에 따라 업데이트되는 포인트가 달라짐
// (다른 컴포넌트에서도 사용하기 때문에 로직 분리)
export const updateUserPoint = async (
  loggedInUserUid: string,
  option: { mode: string },
) => {
  try {
    // 🧡 인자로 받아온 mode 꺼내기
    const { mode } = option;

    // 포인트 가져오기
    const { data, error } = await supabase
      .from("users")
      .select("point")
      .eq("id", loggedInUserUid);

    if (error) {
      throw error;
    }

    const point = data[0].point;
    let updatedPoint;

    // 🧡 mode에 따라 달라지는 포인트 업데이트
    // (댓글은 200포인트, 개인액션 및 게시글 등록은 500포인트 업데이트)
    if (point && mode === "comment") {
      updatedPoint = point + 200;
    } else if (point && (mode === "addAction" || mode === "addPost")) {
      updatedPoint = point + 500;
    }

    const { error: updatePointError } = await supabase
      .from("users")
      .update({ point: updatedPoint })
      .eq("id", loggedInUserUid);

    if (updatePointError) {
      throw error;
    }
  } catch (error) {
    console.error("Error inserting data:", error);
    throw error;
  }
};

공용 모달창 생성

📝 프로젝트 전반에서 사용하고 있는 기본 alert창을 디자인적으로 더 예쁜 모달창으로 리팩토링하기 위해 공용 모달창 생성
-> alert을 사용중인 모든 컴포넌트에서 사용할 수 있게 공용 모달창으로 만듦

// AddPostModal.tsx : _components/community
// (각 컴포넌트에서 사용하는 예시)

import React, { useState } from "react";
import AlertModal from "./AlertModal";

  // 🧡 alert 대체 모달창을 위한 상태관리
  const [isOpenAlertModal, setIsOpenAlertModal] = useState(false);
  const [message, setMessage] = useState("");

	// 🧡 문구 설정, 모달창 열기
    // alert("로그인이 필요합니다.");
    setMessage("로그인이 필요한 서비스입니다.");
    setIsOpenAlertModal(true);

	// 🧡 모달창 컴포넌트는 jsx 최 하단에 위치
      {isOpenAlertModal && (
        <AlertModal
          isOpen={isOpenAlertModal}
          onClose={() => setIsOpenAlertModal(false)}
          message={message}
        />
      )}
// AlertModal.tsx : _components/community
// (모달창 컴포넌트)

import React from "react";

import Image from "next/image";
import { Button, Modal, ModalBody, ModalContent } from "@nextui-org/react";
import logoImg from "../../_assets/image/logo_icon/logo/gray.png";

interface PointModalProps {
  isOpen: boolean;
  onClose: () => void;
  message: string;
}

const AlertModal: React.FC<PointModalProps> = ({
  isOpen,
  onClose,
  message,
}) => {
  return (
    <>
      <Modal placement="center" isOpen={isOpen} onOpenChange={onClose}>
        <ModalContent>
          {(onClose) => (
            <>
              <ModalBody className="flex flex-col gap-14 justify-between items-center py-[40px]">
                <Image src={logoImg} alt="alert" className="w-[15%] h-[7%]" />
                <p className="font-bold text-center text-[16px]">{message}</p>
                <Button
                  type="submit"
                  className="text-gray-500 rounded-full !w-[140px] h-[33px] border border-gray-400 bg-[#EFEFEF]"
                  onClick={() => {
                    onClose();
                  }}
                >
                  확인
                </Button>
              </ModalBody>
            </>
          )}
        </ModalContent>
      </Modal>
    </>
  );
};

export default AlertModal;

✨✨ 결과

  • 기존 : 기본 alert 창

  • 변경 후 : 공통 modal창

profile
무서운 속도로 흡수하는 스폰지 개발자 🧽

0개의 댓글