개인별로 식단을 달력에 적고, 그 달력을 확인하는 기능이 필요했다.
여러가지 달력 라이브러리들을 찾아본 결과 Full-Calendar 가 가장 쓰기 좋고, 편해보여 가져와보았다.
1. 달력 라이브러리 : 풀 캘린더 ( Full-Calendar )
리액트 DOCS
Fullcalendar - Fullcalendar Examples - StackBlitz
Simple example projects for FullCalendar
stackblitz.com
타입스크립트 DOCS
리액트 DOCS : https://fullcalendar.io/docs/react
리액트 EXAMPLE : https://stackblitz.com/github/fullcalendar/fullcalendar-examples/tree/main/react?file=src%2Fevent-utils.js,src%2FDemoApp.jsx
타입스크립트 EXAMPLE : https://github.com/fullcalendar/fullcalendar-examples/tree/main/react-typescript/src
우선 타입스크립트의 DOCS 들을 전부 가져왔다.
DemoApp.jsx
index.css
event-util.js
2. 시작하자마자 에러발생
캘린더의 handleEventClick 의 함수에서 오류가 발생했다.
<오류>
"confirm" 이 esLint 오류가 난다.
<해결>
"window" 객체를 명시하여 해결했다.
<코드>
handleEventClick = (clickInfo: EventClickArg) => {
if (
window.confirm( // window객체를 명시해주었다.
`Are you sure you want to delete the event '${clickInfo.event.title}'`
)
) {
clickInfo.event.remove();
}
3. DOCS 와 파일을 하나씩 뜯어보며, 데이터의 구조를 파악하고, DB에 어떻게 넣을지 고민했다.
[데이터 구성] - "event-utils.js" 에서 확인
let todayStr = new Date().toISOString().replace(/T.*$/, ""); // YYYY-MM-DD of today
{
id: createEventId(), // 이건 고정시키고
title: "All-day event", // "타이틀"
start: todayStr, // 이건 시간
},
[ 데이터 구성 추가 ] - 추후, email 을 이용해 데이터를 걸러낼것이므로
{
id: createEventId(), // 이건 고정시키고
title: "All-day event", // "타이틀"
start: todayStr, // 이건 시간
email: "test@test.com", // 사용자 이메일을 추가시켜서 추후, 필터되게 만들자.
},
4. 확인했다. 이제 캘린더에 값을 넣을때, fetch 를 활용하여 DB 에도 값을 넣어보자.
< helper / calendar-add-to-DB>
export type PostType = {
title: string;
start: string;
email: string;
};
export const addCalendarToDb = async (exerciseData: PostType) => {
await fetch(
"https://do-health-project-default-rtdb.firebaseio.com/user/calendar.json",
{
method: "POST",
body: JSON.stringify(exerciseData),
headers: {
"Content-Type": "application/json",
},
}
);
};
<components / fullcalendar / calendar.tsx >
import { addCalendarToDb } from "../helper/calendar-add-to-DB"; // 내가 넣은 DB에 넣을 함수
...
handleDateSelect = (selectInfo: DateSelectArg) => {
let title = prompt("Please enter a new title for your event");
let calendarApi = selectInfo.view.calendar;
calendarApi.unselect(); // clear date selection
console.log(selectInfo);
if (title) {
calendarApi.addEvent({ // 기존 달력의 입력함수
id: createEventId(),
title,
start: selectInfo.startStr,
end: selectInfo.endStr,
allDay: selectInfo.allDay,
});
addCalendarToDb({ // [코드 추가] 내가 넣은 DB에 넣을 함수
title,
start: selectInfo.startStr,
email: "test@test.com",
});
}
};
5. DB에 넣는 것도 성공했다. 이제 DB의 값을 가져와서 "리덕스 store" 에 넣어본다.
1) 캘린더용 슬라이스 생성
< store/calendar-slice.tsx >
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface PostCalendarType {
title: string;
start: string;
email: string;
}
const initialState: { calendarData: PostCalendarType[] } = {
calendarData: [],
};
const calendarSlice = createSlice({
name: "calendar",
initialState,
reducers: {
updateAllcalendar(state, action: PayloadAction<PostCalendarType[]>) {
state.calendarData = action.payload; // payload 에서 오는 값을, calendarData 로 교체할 것이다.
},
},
});
export const calendarActions = calendarSlice.actions;
export default calendarSlice.reducer;
2) 캘린더용 DB의 값 GET 하기 위한 Thunk(action) 생성
< store/calendar-actions.tsx >
import { Dispatch } from "@reduxjs/toolkit";
import { calendarActions } from "./calendar-slice";
import { useDispatch } from "react-redux"; // useDispath 의 사전 생성
import type { AppDispatch } from "../store/index"; // action 생성자용 Dispatch 타입
let eventGuid = 0;
export function createEventId() {
return String(eventGuid++);
}
export const sendRequest = () => {
return async (dispatch: Dispatch) => { // 타입은 Dispatch 이다.
const fetchData = async () => { // 비동기 함수 만들어서
const response = await fetch(
"https://do-health-project-default-rtdb.firebaseio.com/user/calendar.json"
);
const responseData = await response.json();
const refineData = []; // 파이어 베이스에서 데이터 것 refine
for (const key in responseData) {
refineData.push({
id: createEventId(),
title: responseData[key].start,
start: responseData[key].title,
email: responseData[key].email,
});
}
return refineData;
};
const allCalendar = await fetchData();
dispatch(calendarActions.updateAllcalendar(allCalendar)); // inputData를 집어넣는 action 을 한다.
};
};
// Thunk action 생성자를 만들때, 타입을 지정하기
// https://redux.js.org/tutorials/typescript-quick-start#define-typed-hooks
export const useAppDispatch: () => AppDispatch = useDispatch;
3) store / index.tsx
< store / index.tsx>
import { configureStore } from "@reduxjs/toolkit";
import calendarSlice from "./calendar-slice";
export const store = configureStore({
reducer: {
calendar: calendarSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
6. 값도 가져왔다. 이제 넣어볼..까..? 어? Class 형 컴포넌트네
시련이 닥쳐왔다. Class형 컴포넌트... 잘 모른다... Google 을 뒤적여본다
** useSelector 는 리액트 훅이다. 즉, 리액트 컴포넌트 함수 안에서만 사용가능하고
이 말인 즉슨, 클래스에서는 사용 할 수 없다
좋다! App.js 에서 받아와서, 캘린더로 넘겨주자.
열심히 구글링 해보니, 클래스형 컴포넌트에 props 에는 extend 를 이용한 상속으로 값을 넘겨준다.
아닐 수도 있다.
그래서 App.tsx 에서 Props 를 넘기고
클래스에서 받아온 props 의 타입을 지정하고, 사용해보았다.
< App.tsx > // DB 값 받아온다.
import { Route, Switch } from "react-router-dom";
import { useEffect } from "react";
import Calendar from "./fullcalendar/calendar";
import { sendRequest as sendRequestForCalendars, useAppDispatch } from "./store/calendar-action";
function App() {
const calendarData = useSelector( // 3. 받아온 DB의 캘린더 데이터를, App.tsx 에서 받아온다.
(state: RootState) => state.calendar.calendarData
);
const dispatch = useAppDispatch(); // 1. 타입스크립트의 리덕스툴킷이므로, useAppDispatch() 지정한 타입 사용
useEffect(() => {
dispatch(sendRequestForCalendars()); // 2. DB 의 캘린더 값 받아오는 Thunk
}, [dispatch]);
return (
<div className="App">
<Switch>
...
<Route path={"/calendar"}>
<Calendar calendarData={calendarData} /> // 4. 값 을 넘겨준다.
</Route>
< fullcalendar / calendar.tsx > // 넘겨 받은 값을 사용해보자
...
interface DemoAppState {
weekendsVisible: boolean;
currentEvents: EventApi[];
}
interface Props {
calendarData: { title: string; start: string; email: string }[]; //1. Props type 지정후
}
export default class Calendar extends React.Component< Props, {}, DemoAppState> { //2. Props 타입을 등록하고
state: DemoAppState = {
weekendsVisible: true,
currentEvents: [],
};
render() {
const { calendarData } = this.props; //3. 값을 받아온다.
return (
<div className="demo-app">
{this.renderSidebar()}
<div className="demo-app-main">
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
...
initialEvents={calendarData} // 4. 넣어봤는데, 잠깐 뜨고, 유지가 안된다.
select={this.handleDateSelect}
7. 이젠 Full-Calendar DOCS 를 읽어볼 차례다. 초기 값이 잠깐 뜨고 사라진다.
< 오류발생>
데이터를 넣었지만, 잠깐 뜨고, 유지가 되지 않는다.
< 이유 >
API 에서 강제로, INITIAL_EVETNT 를 초기값으로 고정시킨다.
처음에는, State 의 라이프사이클 때문인줄 알고, 콘솔을 찍어보았다.
아니었다. 무언가 강제로 Initial Calendar Data 를, 상단의 initailEvents 의 초기값으로 고정시킨다.
DOCS 를 뒤져보았다.
야호! 찾았다
넘겨 받은 값을 사용 할때에는, initialEvents 옵션이 아니라, events 옵션으로 변경해야한다.
"alternatively, use the `events` setting to fetch from a feed"
<div className="demo-app">
{this.renderSidebar()}
<div className="demo-app-main">
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
...
events={data} // [바로 여기를 event 로 교체] alternatively, use the `events` setting to fetch from a feed
select={this.handleDateSelect}
성공적이다!

8. 최종 마무리. "로그인한 사람" 만 "email Id" 에 맞는 값을 필터하고, 그 값을 캘린더에 넣는다.
로그인 시에, 토큰을 쿠키에 저장했다.
또한 토큰과 함께, 추가적으로 로그인한 사람의 email 도 함께 쿠키에 저장했다.
const SignInForm = () => {
const [cookies, setCookie] = useCookies(["auth-cookie"]);
...
const submitHanlder = async (e: FormEvent) => {
...
const responseData = await signinHandler(email, password);
if (responseData.idToken) { // idToken 이 있다면 OK
setCookie("auth-cookie", {
idToken: responseData.idToken, // idToken 과
email: responseData.email, // email 을 전부 쿠키에 저장
});
그러므로 내게는, 로그인한 사람의 email 이 있고
그 값을 활용 할 수 있다!
1) App.tsx 에서, 로그인 하지 않은 자를 쫓아낼, Getout 함수와, Data를 필터해서 넣어준다.
< App.tsx >
import { useCookies } from "react-cookie";
function App() {
const [cookies] = useCookies(["auth-cookie"]); // 로그인 쿠키
const history = useHistory();
const calendarData = useSelector( // 캘린더 값 넘겨받기
(state: RootState) => state.calendar.calendarData
);
let filteredCalendar: PostCalendarType[] = []; //캘린더 값 필터하기
if (cookies["auth-cookie"]) {
filteredCalendar = calendarData.filter( // 로그인한 아이디로, 켈린더 데이터 뽑기
(data) => data.email === cookies["auth-cookie"].email
);
}
const getOut = () => { // 넘겨줄 getOut함수 만들기
history.replace("/");
};
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(sendRequestForCalendars()); // 캘린더 값 dispatch 함수
}, [dispatch]);
return (
<div className="App">
<Switch>
<Route path={"/calendar"}>
<Calendar
calendarData={filteredCalendar}
getOut={getOut}
isLogedIn={cookies["auth-cookie"]} // 로그인 했는지 확인
/>
</Route>
2) 캘린더에서, Getout 함수와, 캘린더 Data 를 받아 사용한다.
<components / fullcalendar / calendar.tsx >
import { addCalendarToDb } from "../helper/calendar-add-to-DB"; // 내가 넣은 DB에 넣을 함수
...
interface Props {
calendarData: { title: string; start: string; email: string }[]; // 넘겨받을 값 Type 지정
getOut: () => void; // 넘겨 받은 getOut 함수의 타입 지정
isLogedIn: string; // 넘겨 받은, 로그인확인 변수도 타입 지정
}
export default class Calendar extends React.Component<Props, {}, DemoAppState> { // 타입 넣고
state: DemoAppState = {
weekendsVisible: true,
currentEvents: [],
};
render() {
const calendarData = this.props.calendarData; // 뽑아서 사용
const getOut = this.props.getOut;
const isLogedIn = this.props.isLogedIn;
if (!isLogedIn) { // 로그인 안하면 아웃
getOut();
}
return (
<div className="demo-app">
{this.renderSidebar()}
<div className="demo-app-main">
<FullCalendar
...
events={calendarData} // 캘린더 데이터 넣기
select={this.handleDateSelect}
** [적절한 스타일링 추가, 필요없는 코드 제거] - 사이드바는 제거했다.
'메인-프로젝트 > React - Do-Health 프로젝트' 카테고리의 다른 글
14. 미디어쿼리, 모바일용 레이아웃 헤더 만들기 (0) | 2023.01.04 |
---|---|
13. 달력 라이브러리 [Full-Calendar] - 스타일링 지정하기 (0) | 2023.01.04 |
11. [ 기능 ] FIRE-BASE AUTH 를 사용한, 로그인 작업 시작, with. env 를 사용하여, 중요한 개인정보(API KEY) 숨기기 (0) | 2023.01.04 |
10. *** [ 기능 ] FireBase DB 에서 데이터 가져오기, with. Thunk 액션생성자 ( 리덕스 툴킷에서 타입스크립트와 함께 Thunk 액션생성자를 사용하는 방법) (0) | 2023.01.04 |
9. [ 기능 ] FireBase DB 를 이용하기 위해, 데이터 마이그레이션 (1) | 2023.01.04 |
댓글