이 예제는 하나의 모달창을 모든 페이지에서 띄우는 것 입니다!
배열에 여러 모달창을 넣고 띄우는 것은 아닙니다!
TypeScript 와 Context API 활용하기
를 참고해서 작성했습니다!
폴더구조
[src]
-[app]
-[nextPage]
- [join]
-page.tsx
-[components]
- modal.tsx
-[context]
- modalContext.tsx
- page.tsx
- layout.tsx
modalContext.tsx
(Provider와 Context 파일을 따로 분리해도 되지만 저는 합쳐서 사용했습니다.)
타입을 선언해줍니다.
/* 타입선언 */
//모달 창 유무
type ModalState = { isModal: boolean;}
const initialState: ModalState = {
isModal: false,
}
//모든 액션들을 위한 타입
type ModalAction =
| { type: 'SHOW_MODAL' }
| { type: 'HIDE_MODAL' }
//디스패치를 위한 타입 (Dispatch를 리액트에서 불러올 수 있음)
//액션들의 타입을 Dispatch의 Generics로 설정
type ModalDispatch = Dispatch<ModalAction>
isModal
이 true
일 경우 모달창을 띄워주고
isModal
이 false
일 경우 모달창을 없애줍니다.
Context를 생성합니다.
state 와 dispatch에 대해서 따로따로 생성해주었습니다.
/* Context */
//Context 만들기
const ModalStateContext = createContext<ModalState | null>(null)
const ModalDispatchContext = createContext<ModalDispatch | null>(null)
쉽게 이해해보자면! state는 상태값을 사용할 수 있게 해주고, dispatch는 상태값을 변경할 수 있게 해줍니다!
/* Reducer */
//리듀서
function reducer(state: ModalState, action: ModalAction): ModalState {
switch (action.type) {
case 'SHOW_MODAL':
return {
isModal: true,
}
case 'HIDE_MODAL':
return {
isModal: true,
}
default:
return state
}
}
리듀서 함수를 통해서 isModal
의 상태값을 변경할 수 있게 되었습니다.
Provider 파일을 따로 작성해도 되지만 저는 위 파일에 이어서 작성했습니다!
/* Provider */
//Provider에서 useReducer를 사용하고
//ModalStateContext.Provider 와 ModalDispatchContext.Provider로 children을 감싸서 반환합니다.
//기존에 state와 dispatch를 넣어서 반환하는 것과 동일한 것 같음
export function ModalProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState)
//여기 children에는 하위 컴포넌트들이 다 들어갈 것!
//children -> 리액트에서 자동으로 주입해주는 예약어 개념
return (
<ModalStateContext.Provider value={state}>
<ModalDispatchContext.Provider value={dispatch}>
{children}
</ModalDispatchContext.Provider>
</ModalStateContext.Provider>
)
}
state 와 dispatch에 대한 provider를 모두 감싸주었습니다.
children은 하위 컴포넌트들이라고 생각하면 됩니다.
이 ModalProvider를 이용해서 ModalContext 상태를 이용할 범위를 지정해 줄 것이기 때문에 꼭 export 해주어야 합니다!
/* custom Hook */
// state 와 dispatch를 쉽게 사용하기 위한 커스텀 Hook
export function useModalState() {
const state = useContext(ModalStateContext)
if (!state) throw new Error('useModalState: Cannot find ModalProvider')
return state
}
export function useModalDispatch() {
const dispatch = useContext(ModalDispatchContext)
if (!dispatch) throw new Error('useModalDispatch: Cannot find ModalProvider')
return dispatch
}
각각 state , dispatch를 쉽게 사용하기 위해서 커스텀 훅을 작성했습니다.
'use client'
import React, { useReducer, useContext, createContext, Dispatch } from 'react'
/* 타입선언 */
//모달 창 유무
type ModalState = { isModal: boolean }
const initialState: ModalState = {
isModal: false,
}
//모든 액션들을 위한 타입
// 순서대로 1: 공란이 하나라도 있을 경우 2: 이메일 틀렸을 경우 3: 패스워드 틀렸을 경우
type ModalAction =
| { type: 'SHOW_MODAL' }
| { type: 'HIDE_MODAL' }
//디스패치를 위한 타입 (Dispatch를 리액트에서 불러올 수 있음)
//액션들의 타입을 Dispatch의 Generics로 설정
type ModalDispatch = Dispatch<ModalAction>
/* Context */
//Context 만들기
const ModalStateContext = createContext<ModalState | null>(null)
const ModalDispatchContext = createContext<ModalDispatch | null>(null)
/* Reducer */
//리듀서
function reducer(state: ModalState, action: ModalAction): ModalState {
switch (action.type) {
case 'SHOW_MODAL':
return {
isModal: true
}
case 'HIDE_MODAL':
return {
isModal: true
}
default:
return state
}
}
/* Provider */
//Provider에서 useReducer를 사용하고
//ModalStateContext.Provider 와 ModalDispatchContext.Provider로 children을 감싸서 반환합니다.
//기존에 state와 dispatch를 넣어서 반환하는 것과 동일한 것 같음
export function ModalProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState)
//여기 children에는 하위 컴포넌트들이 다 들어갈 것!
//children -> 리액트에서 자동으로 주입해주는 예약어 개념
return (
<ModalStateContext.Provider value={state}>
<ModalDispatchContext.Provider value={dispatch}>
{children}
</ModalDispatchContext.Provider>
</ModalStateContext.Provider>
)
}
/* custom Hook */
// state 와 dispatch를 쉽게 사용하기 위한 커스텀 Hook
export function useModalState() {
const state = useContext(ModalStateContext)
if (!state) throw new Error('useModalState: Cannot find ModalProvider')
return state
}
export function useModalDispatch() {
const dispatch = useContext(ModalDispatchContext)
if (!dispatch) throw new Error('useModalDispatch: Cannot find ModalProvider')
return dispatch
}
modal.tsx
'use client'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
export default function Modal() {
const router = useRouter()
const onClick = () => {
//모달 버튼
}
return (
<Box
sx={{
width: '100%',
height: '100%',
backgroundColor: 'rgba(40, 40, 40, .1) ',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
position: 'fixed',
}}
>
<Box
sx={{
width: '300px',
height: '300px',
backgroundColor: 'white',
display: 'flex',
flexDirection: 'column',
jjustifyContent: 'center',
alignItems: 'center',
}}
>
<Box sx={{ color: 'black', fontSize: '30px', margin: '10px' }}>
제목
</Box>
<Box sx={{ color: 'black', fontSize: '20px', margin: '10px' }}>
설명
</Box>
<Button onClick={onClick}>확인</Button>
</Box>
</Box>
)
}
앞선 부분에서 만들었던 Hook을 이용해서 모달의 상태를 가져오고 변경하는 코드를 작성해보겠습니다.
//...생략...//
//필요한 훅을 import
import { useModalState
, useModalDispatch } from '@/context/modalContext'
export default function Modal() {
//훅을 불러옵니다.
const dispatch = useModalDispatch()
const state = useModalState()
//...생략...//
이렇게 선언한 state
와 dispatch
를 이용하면 됩니다!
state는 state.isModal
과 같이 변수 값을 가져오면 됩니다.
dispatch 는 dispatch({ type: 'SHOW_MODAL' })
과 같이 상태 값을 액션함수, 리듀서를 통해서 변경해주면 됩니다.
'use client'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import { useModalDispatch } from '@/context/modalContext'
export default function Modal() {
const dispatch = useModalDispatch()
const onClick = () => {
dispatch({ type: 'HIDE_MODAL' })
}
return (
<Box
sx={{
width: '100%',
height: '100%',
backgroundColor: 'rgba(40, 40, 40, .1) ',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
position: 'fixed',
}}
>
<Box
sx={{
width: '300px',
height: '300px',
backgroundColor: 'white',
display: 'flex',
flexDirection: 'column',
jjustifyContent: 'center',
alignItems: 'center',
}}
>
<Box sx={{ color: 'black', fontSize: '30px', margin: '10px' }}>
모달~
</Box>
<Button onClick={onClick}>확인</Button>
</Box>
</Box>
)
}
모달창에서는 모달창의 유무만 결정지으면 되므로 dispatch
만 이용하도록 하겠습니다.
modalContext.tsx
Provider 부분에 모달창을 추가해줍니다.
/* Provider */
//Provider에서 useReducer를 사용하고
//ModalStateContext.Provider 와 ModalDispatchContext.Provider로 children을 감싸서 반환합니다.
//기존에 state와 dispatch를 넣어서 반환하는 것과 동일한 것 같음
export function ModalProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState)
//여기 children에는 하위 컴포넌트들이 다 들어갈 것!
//children -> 리액트에서 자동으로 주입해주는 예약어 개념
return (
<ModalStateContext.Provider value={state}>
<ModalDispatchContext.Provider value={dispatch}>
{children}
{state.isModal && <Modal />}
</ModalDispatchContext.Provider>
</ModalStateContext.Provider>
)
}
isModal
이 true
일 경우엔 모달창이 뜰 것이고
isModal
이 false
일 경우엔 모달창이 뜨지 않을 것 입니다.
저는 모든 파일에서 접근 가능하게 만들고 싶어서 layout.tsx 에 추가해 주었습니다.
layout.tsx
import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { ModalProvider } from '@/context/modalContext'
import Box from '@mui/material/Box'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<ModalProvider>
<body>
<Box
sx={{
width: 300,
height: 300,
flexDirection: 'column',
display: 'flex',
gap: '10px',
padding: '10px',
}}
>
{children}
</Box>
</body>
</ModalProvider>
</html>
)
}
ModalProvider 의 영역에서만 상태값에 접근 가능할까?
답: 아니요!
모든 파일에서 접근가능합니다.
하지만 읽는 데이터 값이 다르게 됩니다.
감싸진 영역 : 초기값
그 이외 영역 : default 값
저는 회원가입 창에서 띄우도록 작성했습니다!
nextPage/join/page.tsx
'use client'
import React, { useState, useRef, ChangeEvent, useContext } from 'react'
import TextField from '@mui/material/TextField'
import Button from '@mui/material/Button'
import Box from '@mui/material/Box'
import Link from 'next/link'
//모달 커스텀 hook
import { useModalDispatch } from '@/context/modalContext'
interface FormValues {
name: string
email: string
password: string
}
export default function Join() {
/* 모달 상태 전역관리 */
const dispatch = useModalDispatch()
const [form, setForm] = useState<FormValues>({
name: '',
email: '',
password: '',
})
const { name, email, password } = form
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
const { value, name } = e.target
setForm({
...form,
[name]: value,
})
}
const passwordRef = useRef<HTMLInputElement | null>(null)
const emailRef = useRef<HTMLInputElement | null>(null)
const onClick = () => {
if (validation(1) && validation(2)) {
if (password === '' || email === '' || name === '')
return dispatch({ type: 'SHOW_MODAL' })
} else {
if (!validation(2)) {
emailRef.current?.focus()
console.log('이메일 틀림')
dispatch({ type: 'SHOW_MODAL' })
} else if (!validation(1)) {
passwordRef.current?.focus()
console.log('비밀번호 틀림')
dispatch({ type: 'SHOW_MODAL' })
}
}
}
//비밀번호, 이메일 유효성
const validation = (type: number) => {
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[\d\W_]).{8,}$/
const emailRegex = /.+@.+/
switch (type) {
case 1:
if (password === '') return true
return passwordRegex.test(password)
case 2:
if (email === '') return true
return emailRegex.test(email)
}
}
return (
<>
<TextField
id="outlined-basic"
label="이름"
variant="outlined"
value={name}
name="name"
onChange={onChange}
/>
<TextField
id="outlined-basic"
label="이메일"
variant="outlined"
value={email}
name="email"
onChange={onChange}
inputRef={emailRef}
error={!validation(2)}
helperText={validation(2) ? '' : '잘못된 이메일 형식 입니다.'}
/>
<TextField
id="outlined-basic"
label="패스워드"
type="Password"
variant="outlined"
value={password}
name="password"
onChange={onChange}
inputRef={passwordRef}
error={!validation(1)}
helperText={
validation(1)
? ''
: '비밀번호는 8자 이상, 특수 문자 1개 이상, 영문 소문자 최소 1개, 영문 대문자 최소 1개의 조건을 만족해야 합니다.'
}
/>
<Button variant="outlined" onClick={onClick}>
가입하기
</Button>
<Link href="/">
<Button variant="outlined">HOME</Button>
</Link>
</>
)
}
(제가 쓴 코드는 다른내용이 좀 더 추가되어 있어서 결과차이 조금 다르게 뜹니다!)
이런식으로 모달창이 뜨게 됩니다!!!!!
제가 올려둔 참고블로그 꼭꼭꼭꼭 보세요,,,,,,,,,,,,,,,,,,,
그럼...
20000,,,