[Spring Boot/React] 카드 신청 페이지 제작
- 카드 신청 폼 만들기
자료 참고
주민등록번호 자동 하이픈 넣기, 뒷자리 *로 마스킹 하기
가입 폼을 만들다보면 핸드폰 번호나 주민등록번호에 자동 하이픈이 생기게 해야 할 때가 있다. 열심히 해놨는데 기획 변경으로 이 부분을 다 날려야 하는 일이 생겨 필요한 코드만 정리해놓기
velog.io
- 문제 : 카드 신청할 때 입력된 카드 정보 데이터들 card 테이블에 insert하고 싶은데 단계별로 넣을 때마다 실시간으로 정보 하나씩 insert해서 db에 들어갔다가 나오는 과정으로 설계하려했지만 더 효율적인 방법이 있을 거 같아 고민
- 해결: 리액트의 useContext사용해 프론트 단에서 데이터 모아서 마지막에 한꺼번에 입력시키고 백으로 insert하기로 함
https://ko.react.dev/reference/react/useContext
useContext – React
The library for web and native user interfaces
ko.react.dev
useContext는 React의 훅(hook) 중 하나로, 컨텍스트(Context) 객체의 현재 값을 가져오기 위해 사용됩니다. 컨텍스트는 컴포넌트 트리 전체에 걸쳐 데이터를 전역적으로 전달할 수 있는 방법을 제공합니다. 이는 props를 통해 데이터를 전달해야 하는 필요성을 줄여줍니다
1. Context 생성 및 제공
import React, { createContext, useContext, useState } from 'react';
const CardContext = createContext(); // 컨텍스트 객체를 생성
// useContext를 사용하여 CardContext의 현재 값을 반환
export const useCardContext = () => {
return useContext(CardContext);
};
export const CardProvider = ({children}) => {
const [produceCardOffer, setProduceCardOffer] = useState({
"card_id":"",
"card_account":"",
"card_balance" : "",
"card_number":"",
"card_password":"",
"card_pickup":"",
"card_status":"",
"cvv_code":"",
"expiration_date":"",
"issue_date":"",
"payment_bank":"",
"payment_date":"",
"pickup_date":"",
"transportation":"",
"card_type_id":"",
"member_id":""
});
return (
<CardContext.Provider value={{produceCardOffer, setProduceCardOffer}}>
{children}
</CardContext.Provider>
)
}
- CardContext를 생성합니다.
- useCardContext는 useContext를 사용하여 CardContext의 현재 값을 반환하는 커스텀 훅입니다.
- CardProvider 컴포넌트는 produceCardOffer 상태와 그 상태를 업데이트하는 setProduceCardOffer 함수를 CardContext.Provider를 통해 하위 컴포넌트에 제공합니다.
2. CardProvider로 Context 감싸기
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { CardProvider } from './CardContext';
ReactDOM.render(
<CardProvider>
<App />
</CardProvider>,
document.getElementById('root')
);
CardProvider로 App을 감싸서 CardContext를 앱 전체에 제공할 수 있도록 합니다.
3. Context 사용하기 (예시: Card1 컴포넌트)
import React, { useState, useEffect } from 'react';
import '../../assets/Card.css';
import Flickity from 'react-flickity-component';
import axios from 'axios';
import { useCardContext } from './CardContext';
import { useNavigate } from 'react-router-dom';
function Card1() {
const [selectedCard, setSelectedCard] = useState(null);
const [cards, setCards] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const {produceCardOffer, setProduceCardOffer} = useCardContext();
let navigate = useNavigate();
const flickityOptions = {
cellAlign: 'right',
pageDots: false,
groupCells: '20%',
selectedAttraction: 0.03,
friction: 0.15,
};
useEffect(() => {
axios.get('/api/cards')
.then(response => {
console.log('Fetched cards:', response.data);
setCards(response.data);
if (response.data.length > 0) {
setSelectedCard(response.data[0]);
}
setIsLoading(false); // 데이터 로드 완료
})
.catch(error => {
console.error('There was an error fetching the cards!', error);
setIsLoading(false); // 데이터 로드 실패
});
}, []);
const handleChange = (index) => {
if (cards.length > 0 && index < cards.length) {
setSelectedCard(cards[index]);
// console.log('Selected card:', cards[index]);
} else {
console.log("카드 데이터가 아직 로드되지 않았습니다.");
}
};
return (
<div className='container'>
<div className='header'>
<div>카드 신청</div>
</div>
<div className='content'>
<div className='carousel-container'>
{isLoading ? (
<div>카드 불러오는 중...</div>
) : (
<Flickity
className='carousel'
options={flickityOptions}
flickityRef={(c) => {
if (c) {
c.on('change', (index) => handleChange(index));
}
}}
>
{cards.map((card, index) => (
<div className="carousel-cell" key={card.cardTypeId}>
<img src={card.cardImg} className="p" alt={card.cardName} />
<div className='card-info'>
<div>카드이름: {card.cardName}</div>
<div>카드사용목적: {card.cardUsage}</div>
<div>카드한도: {card.cardLimit}</div>
<div>연회비: {card.annualFee}</div>
</div>
</div>
))}
</Flickity>
)}
</div>
{selectedCard && (
<div className='selected-card'>
<div>선택한 카드이름: {selectedCard.cardName}</div>
<div>카드사용목적: {selectedCard.cardUsage}</div>
<div>카드한도: {selectedCard.cardLimit}</div>
<div>연회비: {selectedCard.annualFee}</div>
</div>
)}
</div>
<button onClick={() => {
if(selectedCard) {
setProduceCardOffer(prevState => ({
...prevState,
card_type_id:selectedCard.cardTypeId // 선택된 카드 타입
}));
setTimeout(() => navigate('/card2'), 300);
}else{
console.log('카드가 선택되지 않았습니다.');
}
}}>신청하기</button>
<div className='menubar'></div>
</div>
);
}
export default Card1;
- Card1 컴포넌트에서 useCardContext를 사용하여 produceCardOffer와 setProduceCardOffer를 가져옵니다.
- 사용자가 카드 신청 버튼을 클릭하면 selectedCard의 cardTypeId를 produceCardOffer 상태에 업데이트하고, 다음 페이지로 이동합니다.
이와 같은 방식으로 다른 컴포넌트 (Card2, Card3, CardForm, Card5)에서도 useCardContext를 사용하여 produceCardOffer 상태를 가져오고 업데이트합니다. 각 컴포넌트는 필요에 따라 produceCardOffer의 특정 필드를 업데이트하고, 폼 제출 시 해당 데이터를 다음 단계로 전달합니다.
4. 제출
import React from 'react';
import { useCardContext } from './CardContext';
function Card6(props) {
const { produceCardOffer } = useCardContext();
return (
<div>
<h3>카드 발급이 완료되었습니다.</h3>
<pre>{JSON.stringify(produceCardOffer, null, 2)}</pre> {/* 데이터 확인 */}
</div>
);
}
export default Card6;
- <pre> 태그 내에 produceCardOffer 객체를 JSON 형식으로 변환하여 출력합니다. JSON.stringify(produceCardOffer, null, 2)는 produceCardOffer 객체를 JSON 문자열로 변환하며, 두 번째 인자는 들여쓰기(indentation)를 위해 사용됩니다.
- 이렇게 하면 produceCardOffer의 내용을 포맷팅된 JSON 형식으로 화면에 표시할 수 있습니다.