좌충우돌 개발자의 길

[토이프로젝트] Recoil을 이용해 전역 modal 개발하기 본문

PROJECTS/Simple Wallet : 심플한 가계부

[토이프로젝트] Recoil을 이용해 전역 modal 개발하기

sustronaut 2022. 11. 10. 11:03

1. Recoil을 사용하는 이유

이전 레이아웃에서도 나와있듯이 위와 같은 방식으로 모달창을 띄우기로 결정했다.

헤더 부분에 아이콘을 누르면 모달창이 띄워진다.

하지만 나는 알람 모달, 목표 모달로 총 2가지의 모달을 구현해야 해서 이렇게 파일 구조를 짰다.

 

모달창 코드가 돌아가는 과정에 따라 파일을 정리를 하자면

1. Title.js : 이 프로젝트의 헤더 컴포넌트로, 목표 또는 알람 아이콘을 클릭한다.

2.  AlarmModal.js (/GoalModal.js) : 알람 아이콘을 클릭했을 경우 동작하는 파일로 모달창의 공통적인 코드를 모아둔 GlobalModal 컴포넌트와 다른 모달창과 차이있는 부분은 SubmitForm(GoalForm) 컴포넌트를 불러와 알람 모달의 디자인을 구성하고 있다.

3. GlobalModal.js : 모달창의 공통적인 코드를 모아놓은 파일이고, 모달에 입력된 내용을 등록하거나 모달창을 닫는 기능을 이곳에서 한다.

 

이렇게 동작하고 있다.

나는 모달창이 열리고 닫히는 방식을 useState로 구성하려고 했는데 이렇게 파일이 나누어져있어 하나의 파일에서 useState로 관리하기 힘들다고 판단했다.

그래서 드디어 상태관리를 적용해보자고 생각했고, 9월달 세미나에서 발표했던 Recoil을 이용해보자는 결론을 내렸다.

 

2. Recoil을 적용해보자

1. Recoil 설치하기

yarn add recoil

나는 yarn을 사용하고 있기 때문에 yarn으로 설치했다.

 

 

2. Atom을 담아 놓을 파일 구성하기

 

리코일은 리덕스의 store과 같은 기능을 하는 atom이라는 것이 있다.

store라는 폴더를 만들고 그 안에 atom.js를 만들었다.

// atom.js
import { atom } from "recoil";

 let alarmModalState = atom({
  key: "alarmModal",
  default: false,
});

 let goalModalState = atom({
  key: "goalModal",
  default: false,
});

export {alarmModalState, goalModalState};

모달창이 두 개이므로 atom도 2개로 나누어 설정했다.

 

# 트러블 이슈

  • 문제 : export 부분에서 default로 설정하지 않아 에러가 발생
  • 시도1 : 무조건 default를 써야하는 것으로 알고 default를 썼지만 다른 atom이 실행되지 않는 상황이 발생했다. (실패)
  • 시도2 : 두 atom 모두 default를 썼지만 당연히 에러가 났다. default는 1개만 쓸 수 있으니까 (실패)
  • 해결 :  모두 export로만 선언하고, 다른 파일에서 atom을 불러올 때  아래와 같이 { }을 써서 불러오면 실행된다! (성공)
import { alarmModalState, goalModalState } from "../store/atom";

 

3. 아이콘 클릭 시, 값 변경하기

// Title.js

import { AiFillStar, AiFillBell } from "react-icons/ai";
import styled from "styled-components";
import { alarmModalState, goalModalState } from "../store/atom";
import {
  useSetRecoilState,
} from "recoil";

function Title() {
  // 알람 모달 창
  const openAlarmModalHandler = useSetRecoilState(alarmModalState); // 값만 변경 시키기
  const openAlarmModal = () => {
    openAlarmModalHandler(true);
  };

  // 목표 모달 창
  const openGoalModalHandler = useSetRecoilState(goalModalState); // 값만 변경 시키기
  const openGoalModal = () => {
    openGoalModalHandler(true);
  };

  return (
    <TitleContainer>
      <TitlePart>Simple Wallet</TitlePart>
      <IconPart>
        <IconItem onClick={openGoalModal}>
          <AiFillStar size="15" color="#d9d9d9" />
        </IconItem>
        <IconItem onClick={openAlarmModal}>
          <AiFillBell size="15" color="#d9d9d9" />
        </IconItem>
      </IconPart>
    </TitleContainer>
  );
}

export default Title;

Recoil에서 제공하는 useSetRecoilState를 사용했다. 

이 기능은 atom에 만들었던 값을 변경시켜준다.

그래서 아이콘을 클릭했을 때, false->true로 변경한다.

 

4. 3에서 변경된 값을 가져와 모달창 open 여부 파악

// GlobalModal.js

import styled from "styled-components";
import { AiFillStar, AiFillBell } from "react-icons/ai";
import { alarmModalState, goalModalState } from "../../store/atom";
import { useRecoilState } from "recoil";

function GlobalModal({ title, children, icon }) {
    
    // 알람 모달, 목표 모달 각각 상태값 가져오기
    const [alarmOpen, setAlarmOpen] = useRecoilState(alarmModalState); 
    const [goalOpen, setGoalOpen] = useRecoilState(goalModalState); 

    // 닫기 버튼 눌렀을 때
    const handleCanceled = () => {
        if (alarmOpen === true) {
            setAlarmOpen(false)
        }
        else if(goalOpen === true) {
            setGoalOpen(false)
        }
    }

  return (
        <Modal display={(alarmOpen || goalOpen)?"flex":"none"}>
            <ModalContainer>
                <Header>
                {icon === "alarm" ? (
                    <AiFillBell size="15" color="#d9d9d9" />
                ) : (
                    <AiFillStar size="15" color="#d9d9d9" />
                )}
                <HeaderTitle>{title}</HeaderTitle>
                </Header>
                <ModalContents>{children}</ModalContents>
                <ModalButtonContainer>
                    <ModalButton marginRight="10px" backgroundColor="#fff">
                        등록
                    </ModalButton>
                    <ModalButton marginRight="10px" backgroundColor="#d9d9d9" onClick={handleCanceled}>
                        닫기
                    </ModalButton>
                </ModalButtonContainer>
            </ModalContainer>
        </Modal>
  );
}

export default GlobalModal;

Recoil에서 제공하는 useRecoilState를 사용했다.

이 기능은 useState와 비슷하게 작동해서 사용하기 간편하다. 

초기값으로 atom에 저장된 값을 가져오고 handleCanceled 함수에서 set__Open으로 로 열거나 닫을 때 atom 값을 변경한다.

  • true - > 모달 열림
  • false - > 모달 닫힘

 

이렇게 하면 한 파일에서 useState로 관리하지 않더라도 Recoil을 통해 전역적으로 관리가 가능해진다!