last update: 2022.07.19
todo list와 비슷하게 작동하는 D-day를 만들어 봤다. todo list 는 아직 직접 만들어 보진 못해서 모든 기능을 완벽하게 집어넣지는 못했다. 이번에 만들면서 아쉬웠던 점은 추후에 더 배워나가면서 보완해나갈 예정이다.
화면 제일 상단에는 오늘 날짜가 보여진다.
그 다음으로 두개의 입력란이 있는데 첫번째에는 어떤 D-day를 기다리고 있는지 적어주고, 두번째에는 D-day의 날짜를 적어준다.
두개의 입력란에 값을 모두 적어주면 등록버튼이 활성화가 되어 아래에 등록 할 수 있다. (입력값이 없으면 눌러도 등록이 안됨.)
등록버튼을 누르면 입력란이 비워진다.
D-day를 등록하면 D-day 설명과 D-day 날짜, 그리고 D-day까지 몇일 남았는지가 보인다. (D-day는 여러개가 등록이 된다.)
여러개를 등록을 하면 제일 최근 등록한 D-day가 제일 위에 등록이 된다.
만약 오늘 이전의 날짜를 설정하면 D-가 아닌 D+가 된다. (날짜수가 아닌 D-day이기 때문에 2022년 1월 1일이 D-day가 되어 1월 2일부터 D+1이 된다.)
D-day가 여러개라면 화면 최상단의 오늘 날짜와 입력란은 움직이지 않고 D-day들만 스크롤이 된다.
등록된 D-day에 마우스를 올리면 지우기 텍스트가 보이고, 클릭을 하면 클릭한 D-day가 없어진다.
App.js에는 크게 Today와 SetDday를 넣어주었다.
export default function App() { return ( <> <Today /> <SetDday /> </> ); }
const today = new Date(); const todayMonth = today.getMonth(); const todayYear = today.getFullYear(); const todayDate = today.getDate(); const todayDay = today.getDay(); const DAYS = ['일', '월', '화', '수', '목', '금', '토']; export default function Today() { return ( <TodayStyle> <TodayYear>{todayYear}년 </TodayYear> <TodayMonthnDate> {todayMonth + 1}월 {todayDate}일 {DAYS[todayDay]}요일 </TodayMonthnDate> </TodayStyle> ); }
함수
const [isSubmit, setIsSubmit] = useState(false); const [list, setList] = useState([]); const [userInputs, setUserInputs] = useState({ dDayName: '', date: '', }); const handleChange = (e) => { setUserInputs({ ...userInputs, [e.target.name]: e.target.value }); }; const handleSubmit = (e) => { e.preventDefault(); if (userInputs.dDayName === '' || userInputs.date === '') { return; } else { setIsSubmit(true); setList([...list, userInputs]); } for (let i = 0; i < 2; i++) { e.target[i].value = ''; } setUserInputs({ dDayName: '', date: '' }); }; const deleteItem = (e) => { const key = e.target.id; list.splice(key, 1); setList([...list]); };
const [isSubmit, setIsSubmit] = useState(false);
const [list, setList] = useState([]);
const [userInputs, setUserInputs] = useState({
dDayName: '',
date: '',
});
setUserInputs({ ...userInputs, [e.target.name]: e.target.value });
console.log(userInputs) // { dDayName: '', date: '' }
if (userInputs.dDayName === '' || userInputs.date === '') {
return;
} else {
setIsSubmit(true);
setList([...list, userInputs]);
}
for (let i = 0; i < 2; i++) {
e.target[i].value = '';
}
setUserInputs({ dDayName: '', date: '' });
결론적으로, 사용자 화면에서 input에 값을 넣어주면 userInputs 객체에 값이 들어가고, 등록 버튼을 누르면 D-day가 렌더링이 되면서 list배열에 userInputs 객체가 들어간다. 렌더링 됨과 동시에 사용자에게 보여지는 input값은 초기화가 되고, userInputs객체 값도 초기화가 된다.
JSX
return ( <> <FormStyle name='isSubmit' onSubmit={handleSubmit}> <DDayInputStyle name='dDayName' type='text' placeholder="What's your D-day?" onChange={handleChange} /> <DDayInputStyle name='date' type='date' onChange={handleChange} /> <AddDdayBtn type='submit' value='+' /> </FormStyle> <DDayStyle> {isSubmit && list .map((d, idx) => ( <Dday info={d} key={idx} id={idx} delete={deleteItem} /> )) .reverse()} </DDayStyle> </> );
{isSubmit && list.map((d, idx) => <Dday info={d} key={idx} />).reverse()}
이제 Dday.js로 가보자 ✈️
함수
const { dDayName, date } = props.info;
const [hover, setHover] = useState(false);
const [days, setDays] = useState(0);
useEffect(() => {
const today = new Date();
const dday = new Date(`${date} 00:00:00`);
const gapNum = dday - today;
setDays(Math.ceil(gapNum / (1000 * 60 * 60 * 24)));
}, [date]);
useEffect(() => {
const today = new Date();
const dday = new Date(`${date} 00:00:00`);
const gapNum = dday - today;
setDays(Math.ceil(gapNum / (1000 * 60 * 60 * 24)));
}, [date]);
new Date(date)
를 해주고 계산을 했더니 사용자가 입력한 값의 시간이 00:00:00가 아닌 09:00:00로 되어있어서 아침 9시전에는 하루가 아직 안 지난 것처럼 표시가 됐었다. 예를 들어 오늘 날짜를 입력하면 D-day가 되어야 하는데 D-1이 표시가 됐었다.00:00:00
로 설정해주었다.Math.floor
가 아닌 Math.ceil
을 사용한 이유:Math.floor
를 쓰면 0아닌 -1이 된다. 날짜수가 아닌 D-day이기 때문에 오늘을 0으로 기준으로 해야 보기 좋을 것 같다고 생각해서 Math.ceil
을 사용하였다.JSX
return ( <div id={props.id} onClick={props.delete} onMouseEnter={() => { setHover(true); }} onMouseLeave={() => { setHover(false); }} <BoxStyle style={{ transform: hover ? 'scale(1.02)' : 'scale(1)', }} <DDayInfostyle> <DDayNameStyle>{dDayName}</DDayNameStyle> <DateStyle>{date}</DateStyle> </DDayInfostyle> <DDayStyle> <span> D{days >= 0 ? '-' : '+'} {days === 0 ? 'day' : Math.abs(days)} <br /> </span> </DDayStyle> {hover && <CloseBtn>지우기</CloseBtn>} </BoxStyle> </div> );
Math.abs
을 사용해서 절댓값으로 렌더링 해준다.&:hover { background-color: #6b705d; }
position: fixed
해주었다.overflow: hidden
을 주어 border-radius가 박스에 딱 맞게 해주었다.사용자가 input에 값을 입력했을 때 그 값을 받아 화면에 뿌려줘야 하는데 그걸 처음에는 submit버튼을 누르기도 전에 onChange 이벤트가 실행되면서 그냥 그대로 렌더링 해줘버려서 '등록'이 아닌 '실시간 편집'이었었다..
onChange는 값을 바꿔주고 저장하는 역할을 하고, onSubmit은 '등록'하는 역할을 할 수 있도록 역할분배를 해야 한다는 것.
입력값을 받아오고 계산하는 것을 하나의 컴포넌트가 다 해야 하는 건줄 알았는데 받아오는 것은 부모 컴포넌트가 하고 그 값을 따로 계산해서 전달하는 것은 자식 컴포넌트가 할 수 있다는 것. (props와 usueEffect 사용)
자바스크립트로만 구현해 오다가 react를 써보니까 state가 너무 유용하고 간편하다는 점!! (강의로만 보고 직접 구현해보진 않았을 때는 "오..좋네" 정도 였는데 직접 구현을 해보니까 또 느낌이 다르다.)
submit을 하면 input값이 비워지도록 하려면 이벤트 타겟으로 할 수 있다는 점. (onSubmit의 이벤트 타겟과 onChange의 이벤트 타겟은 연관성이 없어서 구현을 못할 줄 알았다.)
state가 여러개 있을 때 하나의 state로 객체로 만들어 묶을 수 있었던 것 같은데 아직 접근 방법과 사용 방법에 익숙치 않아서 dDayName과 date를 제외하고는 전부 따로따로 설정해주었다.
고유한 key값을 주지 못해서 map의 idx로 주었다. 고유한 key값이 없으면 CloseBtn 클릭시 D-day를 DOM에서 완전히 삭제하는 것을 못한다.
- 부모 컴포넌트 SetDday.js에서 map의 인덱스를 props로 전달해주고, onClick 이벤트가 발생하는 요소에 속성으로 인덱스를 넣어 각 D-day의 고유한 id에 접근 할 수 있도록 해준다. 부모 컴포넌트에서 함수를 만들어 주고 인덱스와 함께 자식 컴포넌트에 전달해준다.
const deleteItem = (e) => { const key = e.target.id; list.splice(key, 1); setList([...list]); }; /* e.target.id로 아래 props로 전달했던 id를 가져오고, list배열에서 이 id를 가진 요소를 splice로 지워준 다음 다시 setList 함수를 써서 list를 업데이트 해준다.*/
{isSubmit && list.map((d, idx) => (<Dday info={d} key={idx} id={idx} delete={deleteItem} />)).reverse()} /* map의 idx를 key에 부여하고 id 속성을 따로 만들어 props로 전달. deleteItem 함수도 props로 전달해준다.*/
- 자식 컴포넌트에서는 이 모든걸 props로 받는다.
id는 속성으로 설정해주고, onClick이 됐을 때는 deleteItem함수를 받아와서 실행 되게 한다. 이때 기존에 썼던 boxDelete state는 필요가 없으니 지워준다.<div id={props.id} onClick={props.delete}>...</div>
또 todo list처럼 편집기능도 만들고 싶고, 위치 이동도 만들고 싶은데 그것도 다 고유한 키 값이 필요한 것 같다.
submit 버튼을 누르면 dDayName 입력란에 자동으로 focus가 되게 하고 싶은데 이건 useRef를 써야 하는 것 같다. 아직 익숙치 않아서 쓰질 못했다.
D-day 계산법 말고 날짜수도 만들어 보고 싶다. 예를 들면 '태어난지 몇일째'를 태어난 날을 1일로 해서 D+n 이런식으로...
이번 프로젝트는 일주일에 하나씩 하는 프로젝트이기 때문에 최대한 간단히 하기 위해 기능도 최소한으로 하였다. 구현하는 방법을 알아낸다면 여기서 더 기능도 추가하고 개선 및 수정을 진행 할 예정이다.