Project Memoir/Portfolio (2022)

[Portfolio] React | ContextAPI를 이용한 Modal창 구현

yevdev 2022. 1. 6. 16:47

개발하면서 가장 많은 시간을 뺏긴 것 같았던 모달 창 구현!

ContextAPI로 전역적인 상태를 가지고 오자는 생각을 미리하지 못했어서 더 오래 걸렸던 것 같다!

 

ContextAPI에 관한 공부는 이전에 했으므로 링크달아둠!

https://yexjinitlog.tistory.com/56

 

[React] Study #21 | Context API를 사용한 전역 값 관리

이전 #20 까지 공부했던 챕터에서 까지, 특정 함수를 특정 컴포넌트를 거쳐서 원하는 컴포넌트에게 전달해주었다. 컴포넌트 한개 정도를 거쳐서 전달하는 것은 사실 큰 불편함은 없지만, 만약 3~4

yexjinitlog.tistory.com

 


 

구현하기에 앞서,,

구현할 모달창을 확인해 보자

이전 포스팅에서

이전 포스팅에서 만들었던 슬라이더

이런 Carousel Slider을 구현했었다. 이제 이 항목을 클릭하면

구현할 모달창

이렇게 모든 화면을 어둡게 하면서 자세한 내용이 띄어지는 모달창을 구현해 보자!

 

 

 

💡 근데 왜 ContextAPI와 같은 전역적인 상태관리가 필요했을까?
- 모달창을 구현하면서 자식 컴포넌트에서 눌렀던 항목이 부모 컴포넌트로 데이터를 전달하기에 너무 많은 props를 거쳐야했기 때문
- ContextAPI를 통해, 모든 컴포넌트에서 현재 모달창의 상태 (open & close) 와 현재 누른 항목의 id 값을 확인할 수 있도록 할 것이다.

 

 

 

1️⃣ ContextAPI 만들기

  • Modal의 상태, 눌러진 항목의 id값, projects데이터들을 상태로 저장
  • 이러한 상태들을 이용한 actions
import React, { useReducer, createContext, useContext } from "react";

const initState = {
  ModalState: false,
  id: null,
  projects: [
    {
        id: 1,
        title: '동아리 홍보 페이지',
        host: '[2021 EndlessCreation]',
        image: 'img/projects/Ec.png'
    },
    {
        id: 2,
        title: '우유마켓',
        host: '멋쟁이 사자처럼 9기',
        image: 'img/projects/Wooyoo.png'
    },
    {
        id: 3,
        title: 'AI를 활용한 과제 협업, 평가 학습 블로그',
        host: '2021 한이음 공모전',
        image: 'img/projects/Join.png'
    },
    {
        id: 4,
        title: 'Rendering',
        host: '서울과기대 프로젝트',
        image: 'img/projects/Rendering.png'
    }
]
};

function StateReducer(state, action) {
    switch (action.type) {
      case "GET_STATE":
        return {
            state,
        };
      case "MODAL_OPEN":
        return {
          ...state,
          ModalState: true,
        };
      case "MODAL_CLOSE":
        return {
          ...state,
          ModalState: false,
        };
      case "CHANGE_ID":
        return {
          ...state,
          id: action.id,
        };

      default:
        throw new Error(`Unhanded action type : ${action.type}`);
    }
  }

const ModalStateContext = createContext(null);
const ModalDispatchContext = createContext(null);

export function ModalContext({ children }) {
    const [state, dispatch] = useReducer(StateReducer, initState);
  
    return (
      <ModalStateContext.Provider value={state}>
        <ModalDispatchContext.Provider value={dispatch}>
          {children}
        </ModalDispatchContext.Provider>
      </ModalStateContext.Provider>
    );
  }
  
  export function useModalState() {
    const context = useContext(ModalStateContext);
    return context;
  }
  
  export function useModalDispatch() {
    const context = useContext(ModalDispatchContext);
    return context;
  }

 

 

 

2️⃣ 띄어질 모달창

  • contextAPI에서 선택된 항목의 id 값을 가져옴
  • 취소버튼을 클릭하면 모달이 닫히는 "MODAL_CLOSE" 액션을 디스패치
import React,{ useCallback } from 'react'
import styled from 'styled-components'
import { useModalState, useModalDispatch } from "../../context/modalContext";
import { Projects } from '../../data'
import Contents from './ModalContents'

const Block = styled.div`
display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 99;
  background: rgb(25, 25, 25, 0.64);
`;

const ModalBlock = styled.div`
  position: fixed;
  background-color: #fff;
  width: 987px;
  height: 655px;
  border-radius: 15px;
  &::-webkit-scrollbar {
    width: 10px;
  }
  &::-webkit-scrollbar-track {
    background-color: transparent;
  }
  &::-webkit-scrollbar-thumb {
    border-radius: 5px;
    background-color: rgba(0, 0, 0, 0.25);
  }
  &::-webkit-scrollbar-button {
    width: 0;
    height: 0;
  }
`;

const Content = styled.div`
  position: fixed;
  width: 100%;
  padding: 10px 0px 0px 25px;
  color: black;
  font-size: 20px;
`;
const AddButton = styled.button`
  position: absolute;
  width: 90px;
  font-size: 15px;
  background: none;
  border: none;
  color: red;
`;


function ProjectModal() {
  const State = useModalState();
  const ModalDispatch = useModalDispatch();
  const ModalState = State.ModalState;
  const id = State.id;

  const { list } = Projects();

  const project = list.find(project => project.id===id); // 배열에서 id로 객체 찾아내오기 find 함수 사용!


  const closeModal = useCallback(() => {
    ModalDispatch({
      type: "MODAL_CLOSE",
    });
  }, [ModalDispatch]);

    return (
        <>
      {ModalState && (
        <Block>
          <ModalBlock>
            <Content>
              <Contents project={project} />
            </Content>
            <AddButton onClick={closeModal}>취소</AddButton>
          </ModalBlock>
        </Block>
      )}
    </>
    )
}

export default ProjectModal

 

 

 

3️⃣ 모달을 띄우기위한 클릭 이벤트가 있는 컴포넌트

  • 눌러질 항목들을 포함하는 컴포넌트 -> 항목을 누르면 모달창의 띄어짐
  • 항목을 누르면 id값을 contextAPI 상태의 id값으로 설정
  • 이 두 기능을 하나의 클릭이벤트로 구현 (하나의 항목 클릭) 
import React, { useCallback } from 'react'
import styled from 'styled-components'
import { useModalDispatch } from '../../context/modalContext'

const Title = styled.div`
  width: 460px;
  position: fixed;
  text-align: center;
  flex: 1;
  font-size: 1.7rem;
  letter-spacing: 0px;
  color: #ffffff;
  opacity: 1;
  font-weight: 500;
  margin-top: -190px;
  visibility: hidden;

`
const Host = styled.div`
  width: 460px;
  position: fixed;
  margin: 0 auto;
  text-align: center;
  flex: 1;
  font-size: 1.2rem;
  letter-spacing: 0px;
  color: #ffffff;
  opacity: 1;
  font-weight: 500;
  margin-top: -140px;
  visibility: hidden;
`

const Img = styled.div`
cursor: pointer;
width: 460px;
height: 300px;

filter: drop-shadow(4px 4px 4px rgba(0.25, 0.25, 0.25, 0.25));
margin-bottom: 80px;

img {
    width: 100%;
    height: 100%;
    opacity: 1;
  }

:hover {
    img {
      opacity: 1;
      transition: all 0.5s;
      filter: brightness(50%); 
    }
    ${Title} {
      transition: all 0.5s;
      visibility: visible;
    }
    ${Host} {
      transition: all 0.5s;
      visibility: visible;
    }
  }

`


function ProjectBox({item}) {

  const ModalDispatch = useModalDispatch();

  const id = item.id;

  const changeid = useCallback(() => {
    ModalDispatch({
      type: "CHANGE_ID",
      id,
    });
  }, [ModalDispatch,id]);

  const openModal = useCallback(() => {
    ModalDispatch({
      type: "MODAL_OPEN",
    });
  }, [ModalDispatch]);


  function ClickEvent(id) {
    openModal();
    changeid(id);
  }

    return (
      <>
        <div>
            <Img onClick={() => ClickEvent(item.id)}>
                <img src={item.image} />
                <Title>{item.title}</Title>
                <Host>{item.host}</Host>
            </Img>
        </div>
      </>
    )
}

export default ProjectBox

 

 

구현완료🎃