프로젝트를 할 때 react-datepicker를 사용할 때가 빈번합니다.
커스텀하기에 가장 flexible한것 같아서 사용하고 있는데
이번에는 redux-toolkit과 연결해서
날짜를 보내주고, 날짜를 이용해 데이터를 fetch하였습니다.
여기서 처음에는 dayjs를 감싸서 toolkit에 보내주었을 때
"A non-serializable value was detected in the state" when using redux toolkit 와 같은 warning을 발생시켜
해결 방법으로 new Date()를 이용해 겉을 더 감싸서 보내주어 문제 없이 작동했습니다.
구현 영상
부모 코드
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from "react-redux";
import { addChosenDate } from "../../../../features/mobile/meal/addEatSlice"
import dayjs from 'dayjs';
import 'dayjs/locale/ko';
import CalenderDialog from '../common/CalenderDialog';
const CalenderCard = () => {
const dispatch = useDispatch()
const [openCalender, setOpenCalender] = useState(false)
const { chosenDate } = useSelector((state) => state.addEat) //redux-toolkit data
const [startDate, setStartDate] = useState(new Date()) //컴포넌트 내부에서 useState로 날짜 지정
const handleChange = (e) => {
setStartDate(e)
}
const onOpenCalender = () => {
setOpenCalender(true)
}
const onIncreaseDay = () => { //전날 날짜
setStartDate(new Date(dayjs(startDate).add(1, 'day')))
dispatch(addChosenDate(new Date(dayjs(startDate).add(1, 'day'))))
}
const onDecreaseDay = () => { //다음날 날짜
setStartDate(new Date(dayjs(startDate).add(-1, 'day')))
dispatch(addChosenDate(new Date(dayjs(startDate).add(-1, 'day'))))
}
const onSaveChosenDate = (date) => { //달력에서 확인 눌렀을 경우 날짜 저장
dispatch(addChosenDate(date))
setOpenCalender(false)
}
useEffect(() => { //redux-toolkit chosendate 변경시마다 fetchMeal 실행
if (!chosenDate) {
dispatch(addChosenDate(new Date()))
} else {
let params = { userId, standardDate: chosenDate }
dispatch(fetchMeal(params))
}
}, [chosenDate])
return (
<>
<div>
<img onClick={onDecreaseDay} src={'/images/prev_arrow.svg'} alt='이전 아이콘' />
<div onClick={onOpenCalender}>
<img src={'/images/calender_black.svg'} alt='달력 아이콘' />
<span>{dayjs(startDate).format(`YYYY${'년'} M${'월'} D${'일'}`) || dayjs().format(`YYYY${'년'} M${'월'} D${'일'}`)} </span>
</div>
<img onClick={onIncreaseDay} src={'/images/next_arrow.svg'} alt='다음 아이콘' />
</div>
{openCalender && <CalenderDialog startDate={startDate} handleChange={handleChange} onSaveChosenDate={onSaveChosenDate} />}
</>
);
};
export default CalenderCard;
자식 컴포넌트
캘린더에 애니메이션 효과를 주기 위하여 framer-motion 라이브러리를 사용하였습니다.
import React from "react";
import { motion } from "framer-motion";
import DatePicker, { registerLocale } from "react-datepicker";
import ko from 'date-fns/locale/ko';
import "react-datepicker/dist/react-datepicker.css";
import Btn2 from "./Btn2";
const CalenderDialog = ({ onSaveChosenDate, startDate, handleChange }) => {
registerLocale("ko", ko); //한국어 설정
const formatDate = (d) => { //달력 년, 월, 일 header
const date = new Date(d);
const monthIndex = date.getMonth() + 1;
const year = date.getFullYear();
return `${year}년 ${`0${monthIndex}`.slice(-2)}월`;
}
return (
<>
<div>
<motion.div //애니메이션 효과
initial={{ opacity: 0, y: 0 }}
animate={{ opacity: 1 }}
transition={{ ease: "easeOut", duration: 1 }}
className="bg-white rounded text-gray-500">
<div className='text-center m-4'>
<DatePicker
selected={startDate}
onChange={handleChange}
disabledKeyboardNavigation //다른 월의 같은 날짜시 자동 selected 되는 현상 방지
locale="ko"
inline
maxDate={new Date()}
popperModifiers={{ //화면을 벗어나지 않도록 하는 설정
preventOverflow: { enabled: true }
}}
popperPlacement="auto" //화면 중앙에 팝업이 출현
renderCustomHeader={({ date, decreaseMonth, increaseMonth }) => ( //header 커스텀 설정
<div>
<div onClick={decreaseMonth}>
<svg xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 19l-7-7 7-7" />
</svg>
</div>
<div>{formatDate(date)}</div>
<div onClick={increaseMonth}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" />
</svg>
</div>
</div>)}
/>
<Btn2 title={`확인`} onAction={() => onSaveChosenDate(startDate)} option={`1`} />
</div>
</motion.div>
</div >
</>
);
};
export default CalenderDialog;
react-toolkit slice
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import api from '../../../common/api';
import dayjs from 'dayjs';
const initialState = {
chosenDate: null,
}
const addEatSlice = createSlice({
name: "식사입력",
initialState,
reducers: {
addChosenDate: (state, action) => {
state.chosenDate = action.payload
},
},
extraReducers: { ... },
})
export const { addChosenDate } = addEatSlice.actions
export default addEatSlice.reducer
다시 업데이트
chosenDate 역시 날짜 형식으로 "A non-serializable value was detected in the state" when using redux toolkit 와 같은 warning을 발생시켰습니다.
해결방법
toolkit 사용안할 경우
toolkit의 chosenDate와 addChosenDate를 없애고,
최상단 부모에서 stateDate로만 사용하여 props로 자식에게 내려주어 사용하였습니다.
toolkit 사용할 경우
react-toolkit에서 Sun Mar 13 2022 10:37:01 GMT+0900 (한국 표준시)와 같은 날짜 타입 데이터 전송시
위와 같은 에러가 발생하여 dayjs 를 이용해 220313 으로 데이터를 전송한 후
컴포넌트에서 가져올때 다시 new Date('220313')을 date 타입으로 바꿔줘야 합니다.
'개발 > React' 카테고리의 다른 글
[react] react의 scroll 이벤트 사용법 (0) | 2022.03.25 |
---|---|
[react] html 카메라 갤러리 이미지 불러오기 보여주기 DB 보내기 (0) | 2022.03.18 |
[react] react에서 svg 불러오는 방법 (0) | 2022.03.10 |
[react] 체크박스 구현 (ft. 전체 선택, 전체 해제) (2) | 2022.03.07 |
[react] useEffect 안에서 scroll으로 인한 렌더링 문제 (0) | 2022.02.24 |
댓글