개발하면서 가장 많은 시간을 뺏긴 것 같았던 모달 창 구현!
ContextAPI로 전역적인 상태를 가지고 오자는 생각을 미리하지 못했어서 더 오래 걸렸던 것 같다!
ContextAPI에 관한 공부는 이전에 했으므로 링크달아둠!
https://yexjinitlog.tistory.com/56
구현하기에 앞서,,
구현할 모달창을 확인해 보자
이전 포스팅에서
이런 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
구현완료🎃
'Project Memoir > Portfolio (2022)' 카테고리의 다른 글
[Portfolio] Github 으로 React 프로젝트 호스팅하기 (0) | 2022.01.14 |
---|---|
[Portfolio] React | Aos를 사용하여 애니메이션 효과 주기 (0) | 2022.01.12 |
[Portfolio] React | React-scroll 을 이용해서 원하는 영역으로 이동 (0) | 2022.01.10 |
[Portfolio] React | react-slick으로 carousel 구현하기 (0) | 2022.01.05 |
[Portfolio] 포트폴리오를 만들어보자! (0) | 2022.01.04 |