오늘은 앞으로 오후 4시부터 5시에 자바스크립트 공부를 팀원들이랑 같이 진행하려고 정한 자바스크립트 스터디모임의 첫번째 날이다.
다들 4시에 만나서 4시 반까지는 개인적으로 같이 공부하기로 한 개념에 대해서 공부하고 4시반부터 5시까지는 공부한 내용에 대해서 서로 제대로 이해하고 있는지 궁금한 점이나 모르는 점에 대해서는 알고 있는 사람이 설명해주는 방식으로 진행했다. 다들 이 방식에 대해서 괜찮다고 이야기해주었고 당분간은 이대로 진행해볼까 한다.
const PaymentForm = () => {
const [objectState, setObjectState] = useState({
name: '',
price: 0,
today: new Date().toISOString().split('T')[0]
})
const inputHandler = (e) => {
const { name, value } = e.target
setObjectState((prevState) => ({ ...prevState, [name]: value }))
}
const buttonSubmitHandler = (event) => {
event.preventDefault()
console.log(objectState)
setObjectState({
name: objectState.name,
price: objectState.price,
today: objectState.today
})
}
...
state를 객체로 관리하면서 input에 전달하는 change 함수도 통일 시키면 좋을 것 같아서 위와 같이 리팩토링..
그렇게 하던 중간에 경고 메세지 발생
specified value "Thu Oct 12 2023 11:49:38 GMT+0900 (\uD55C\uAD6D \uD45C\uC900\uC2DC)"
does not conform to the required format, "yyyy-MM-dd".
이 경고 메세지는 날짜 형식이 안 맞는다는 소리인 것 같은데.. GPT한테 물어보니까
이 경고 메시지는 <input type="date"> 요소의 value에 설정된 날짜 형식이 지정된 형식("yyyy-MM-dd")과 일치하지
않을 때 발생합니다. <input type="date"> 요소의 value 속성은 날짜를 "yyyy-MM-dd" 형식으로 가져야 합니다.
그러나 objectState.today는 JavaScript의 Date 객체이며 이 형식과 일치하지 않습니다.
그래서 아래 코드를 이용한다.
today: new Date().toISOString().split('T')[0]
toISOString()은 자바스크립트의 Date 객체를 ISO 8601 형식의 문자열로 변환한다. 이 형식은 날짜와 시간 정보를 정확하게 표현하는 표준 형식이다. "yyyy-MM-ddTHH:mm:ss.sssZ”
그리고 split으로 T를 구분자로 해서 나누고 앞 부분만 가지고 오면 “yyyy-MM-dd” 형식이 된다. 그렇게 하면 new Date()로 선언한 오늘 날짜가 초기값으로 들어가게 되고 형식도 맞게 된다.
그리고 사실 그냥 경고창이 뜨지 않게 하려면 초기값을 new Date가 아니라 빈 문자열로 전달하면 된다.
<PaymentForm getPaymentFormDate={getPaymentFormData} />
<Expenses items={expenses} />
const PaymentForm = ({ getPaymentFormData }) => {
const [objectState, setObjectState] = useState({
name: '',
price: 0,
today: new Date().toISOString().split('T')[0]
})
...
getPaymentFormData가 아니라 getPaymentFormDate로 잘못 전달해서 알게된 사실..
하위 컴포넌트에서 받을 때는 getPaymentFormDate={getPaymentFormData} 이 중에 앞 부분의 이름으로 받게되는 것이고 뒤에 인자는 실제 상위 컴포넌트에서 전달하는 함수 이름이 되는 것 같다.
즉, 앞 부분은 하위 컴포넌트에 전달할 이름 뒷 부분은 상위 컴포넌트에서 전달받는 함수나 변수로 생각하면 될 것 같다.
아니면 아래 코드와 같이 props로 받아서 가지고 와도 된다. { } 객체 형식으로 받아오는 것이 아니라..
const PaymentForm = (props) => {
const [objectState, setObjectState] = useState({
name: '',
price: 0,
today: new Date().toISOString().split('T')[0]
})
...
const buttonSubmitHandler = (event) => {
event.preventDefault()
props.getPaymentFormData(objectState)
setObjectState({
name: objectState.name,
price: objectState.price,
today: objectState.today
})
}
input의 value값들을 objectState 상태값에 Form의 input 데이터를 관리하고 그걸 getPaymentFormData의 함수에 전달하면서 상위 컴포넌트인 App.js에 전달해서 그걸 Expenses 컴포넌트의 prop으로 전달하는 과정에서 input의 date 타입으로 생성한 value 값이 new Date 객체로 넘어가지 않고 string으로 넘어갔다.
import React, { useRef, useState } from 'react'
import './PaymentForm.css'
const PaymentForm = (props) => {
const [objectState, setObjectState] = useState({
id: 0,
name: '',
price: 0,
date: new Date().toISOString().split('T')[0]
})
const [isSubmit, setIsSubmit] = useState(true)
const nameRef = useRef(null)
const inputHandler = (e) => {
const { name, value } = e.target
setIsSubmit(true)
setObjectState((prevState) => ({ ...prevState, [name]: value }))
}
const buttonSubmitHandler = (event) => {
event.preventDefault()
props.getPaymentFormData(objectState)
setObjectState({
id: Math.random(),
name: objectState.name,
price: objectState.price,
date: objectState.date
})
setIsSubmit(false)
nameRef.current.focus()
}
return (
<div className="new-payment">
<form onSubmit={buttonSubmitHandler}>
<div className="new-payment__controls">
<div className="new-payment__control">
<label>이름</label>
<input
type="text"
onChange={inputHandler}
value={isSubmit ? objectState.name : ''}
name="name"
ref={nameRef}
/>
</div>
<div className="new-payment__control">
<label>금액</label>
<input
type="number"
min="0.01"
step="0.01"
onChange={inputHandler}
value={isSubmit ? objectState.price : 0}
name="price"
/>
</div>
<div className="new-payment__control">
<label>날짜</label>
<input
type="date"
min="2019-01-01"
max="2023-12-31"
onChange={inputHandler}
value={
isSubmit
? objectState.date
: new Date().toISOString().split('T')[0]
}
name="date"
/>
</div>
</div>
<div className="new-payment__actions">
<button type="submit">결제 추가</button>
</div>
</form>
</div>
)
}
export default PaymentForm
그렇기 때문에 Expenses의 컴포넌트의 하위 컴포넌트로 ExpensesDate 컴포넌트가 있는데, 해당 컴포넌트에서 props로 date를 받아서 toLocaleString이랑 getFullYear 메서드를 사용하는데 넘어온 date가 string이다 보니까 toLocaleString이랑 getFullYear가 메서드가 아니라는 오류가 계속 발생했다. 그래서 props.date를 new Date로 감싸서 해결..
const ExpenseDate = (props) => {
const month = new Date(props.date).toLocaleString('ko-KR', { month: 'long' })
const day = new Date(props.date).toLocaleString('ko-KR', { day: '2-digit' })
const year = new Date(props.date).getFullYear()
return (
<div className="expense-date">
<div className="expense-date__month">{month}</div>
<div className="expense-date__year">{year}</div>
<div className="expense-date__day">{day}</div>
</div>
)
}
export default ExpenseDat
submit하고 focus가 name input에 유지될 수 있도록 하기 위해서 useRef를 이용
const nameRef = useRef(null)
<div className="new-payment__control">
<label>이름</label>
<input
type="text"
onChange={inputHandler}
value={isSubmit ? objectState.name : ''}
name="name"
ref={nameRef}
/>
</div>
const buttonSubmitHandler = (event) => {
event.preventDefault()
props.getPaymentFormData(objectState)
setObjectState({
id: Math.random(),
name: objectState.name,
price: objectState.price,
date: objectState.date
})
setIsSubmit(false)
nameRef.current.focus()
}
input에 useRef로 선언한 nameRef 값을 ref={nameRef} 와 같이 선언하고, submit 함수에서 current 값에 focus 메서드를 호출함으로써 submit 이후에 name input으로 focus가 유지될 수 있도록 구현
그리고 input의 값들을 초기화하기 위해 isSubmit이라는 상태값을 선언해서 isSubmit이 제출하고 나서 false로 변경되면 input의 value에 isSubmit의 조건에 따라 다른 값을 가지고 있을 수 있도록 삼항 연산자를 활용해서 구현
return (
<div className="new-payment">
<form onSubmit={buttonSubmitHandler}>
<div className="new-payment__controls">
<div className="new-payment__control">
<label>이름</label>
<input
type="text"
onChange={inputHandler}
value={isSubmit ? objectState.name : ''}
name="name"
ref={nameRef}
/>
</div>
<div className="new-payment__control">
<label>금액</label>
<input
type="number"
min="0.01"
step="0.01"
onChange={inputHandler}
value={isSubmit ? objectState.price : 0}
name="price"
/>
</div>
<div className="new-payment__control">
<label>날짜</label>
<input
type="date"
min="2019-01-01"
max="2023-12-31"
onChange={inputHandler}
value={
isSubmit
? objectState.date
: new Date().toISOString().split('T')[0]
}
name="date"
/>
</div>
</div>
<div className="new-payment__actions">
<button type="submit">결제 추가</button>
</div>
</form>
</div>
)