상품관리 - Part1 상품등록
ㆍProject Diary/React + Firebase (Snack ShoppingMall)
쇼핑몰의 상품 등록 기능을 구현 기록
Firebase를 이용해 상품 데이터를 관리하고, React와 Redux를 사용하여 데이터를 처리하는 방법을 다룹니다.
1. Firebase 설정
import firebase from "firebase/compat/app";
import "firebase/compat/database";
import "firebase/compat/storage";
// Firebase 설정 초기화
const firebaseConfig = firebase.initializeApp({
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
databaseURL: "https://YOUR_PROJECT_ID.firebaseio.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT_ID.appspot.com",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID",
measurementId: "YOUR_MEASUREMENT_ID",
});
// Firebase 데이터베이스와 스토리지 참조 생성
const kuwazawaDB = firebaseConfig.database();
export const kuwazawa_productDB = kuwazawaDB.ref("kuwazawa_products");
export const oStorage = firebaseConfig.storage();
2. 상품 등록 폼 컴포넌트 구현
상품을 등록하는 폼을 생성합니다. 사용자가 입력한 데이터를 Firebase 데이터베이스에 저장하고, 파일을 스토리지에 업로드합니다
const OnlineShopInsertSection = () => {
const navigate = useNavigate();
const [product, setProduct] = useState({
category: "클래식상품",
name: "",
price: "",
description: "",
inventory: "",
photo: "",
detailPhotos: [],
});
const [photoValue, setPhotoValue] = useState("");
const [detailPhotos, setDetailPhotos] = useState([]);
// 입력 필드 변경 핸들러
const handleChange = (e) => {
const { value, name } = e.target;
setProduct((product) => ({ ...product, [name]: value }));
};
// 대표 사진 파일 변경 핸들러
const handleFileChange = (e) => {
const file = e.target.files[0];
setProduct((prevProduct) => ({ ...prevProduct, photo: file }));
setPhotoValue(e.target.value);
};
// 상세 사진 파일 변경 핸들러
const handleDetailFileChange = (e) => {
const files = e.target.files;
setDetailPhotos(Array.from(files));
};
// 폼 제출 핸들러
const onSubmit = async (e) => {
e.preventDefault();
const addProduct = { ...product, id: Date.now() };
try {
const storageRef = oStorage.ref();
// 대표 사진 업로드
if (product.photo) {
const fileRef = storageRef.child(product.photo.name);
await fileRef.put(product.photo);
addProduct.photo = await fileRef.getDownloadURL();
}
// 상세 사진 업로드
if (detailPhotos.length > 0) {
const detailPhotoURLs = [];
await Promise.all(
detailPhotos.map(async (file, index) => {
const fileName = `detailPhoto${index + 1}_${Date.now()}_${file.name}`;
const detailFileRef = storageRef.child(fileName);
await detailFileRef.put(file);
detailPhotoURLs.push(await detailFileRef.getDownloadURL());
})
);
addProduct.detailPhotos = detailPhotoURLs;
}
// Firebase 데이터베이스에 상품 데이터 추가
await kuwazawa_productDB.push(addProduct);
// 폼 초기화
setProduct({
category: "클래식상품",
name: "",
price: "",
description: "",
inventory: "",
photo: "",
detailPhotos: [],
});
setPhotoValue("");
setDetailPhotos([]);
navigate("/product");
} catch (error) {
console.log("오류 : ", error);
}
};
return (
<OnlineShopInsertSectionBlock>
// form 생략
</OnlineShopInsertSectionBlock>
);
};
export default OnlineShopInsertSection;
3. Redux 설정 및 상품 데이터 패치
상품 데이터를 관리하기 위해 Redux를 사용합니다. 상품 등록 후 상품 리스트를 업데이트하기 위해 Redux를 설정하고, Firebase에서 데이터를 가져옵니다.
Redux Slice 생성
import { createSlice } from "@reduxjs/toolkit";
import { kuwazawa_productDB } from "@/assets/firebase";
const productSlice = createSlice({
name: "products",
initialState: {
products: [],
},
reducers: {
initProducts(state, action) {
state.products = action.payload;
},
},
});
export const { initProducts } = productSlice.actions;
export const fetchProducts = () => async (dispatch) => {
try {
// Firebase 데이터베이스에서 상품 데이터를 실시간으로 가져옴
kuwazawa_productDB.on("value", (snapshot) => {
// snapshot.val()을 통해 데이터를 객체 형태로 가져옴
const productsObj = snapshot.val();
// 객체를 배열 형태로 변환, key를 포함한 새로운 객체 배열 생성
const productsArr = Object.entries(productsObj).map(([key, value]) => {
return { key, ...value };
});
// 변환된 배열을 Redux 스토어에 저장
dispatch(initProducts(productsArr));
});
} catch (error) {
console.error("Error fetching products:", error);
}
};
export default productSlice.reducer;
fetchProduct 코드에서 각 부분을 자세히 설명하겠습니다.
kuwazawa_productDB.on("value", callback):
- Firebase 데이터베이스에서 value 이벤트를 통해 데이터를 실시간으로 가져옵니다.
- 데이터가 변경될 때마다 callback 함수가 호출됩니다.
snapshot.val():
- snapshot 객체를 통해 데이터베이스에서 가져온 데이터를 얻습니다.
- val() 메서드는 데이터를 객체 형태로 반환합니다.
Object.entries(productsObj).map(([key, value]) => { ... }):
- 객체를 배열로 변환합니다.
- Object.entries() 메서드는 객체의 키-값 쌍을 배열로 반환합니다.
- map() 메서드를 사용하여 각 키-값 쌍을 새로운 객체로 변환합니다.
- 이때 key와 value를 분해하여 사용합니다.
dispatch(initProducts(productsArr)):
- 변환된 배열을 Redux 스토어에 저장합니다.
- initProducts 액션을 디스패치하여 스토어의 상태를 업데이트합니다.
4. 상품 리스트 컴포넌트 구현
Redux를 통해 패치한 상품 데이터를 리스트로 표시하는 컴포넌트를 구현합니다.
const OnlineShopsection = ({ title }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector((state) => state.members.user);
const allData = useSelector((state) => state.products.products);
const [products, setProducts] = useState(allData);
const [loading, setLoading] = useState(false);
// 상품 정렬
const sortProduct = (keyname) => {
setProducts((products) => {
// 기존 상품 배열을 복사하여 새로운 배열 생성
let sortedProducts = [...products];
// 배열을 정렬
sortedProducts.sort((a, b) => (a[keyname] < b[keyname] ? -1 : 1));
// 정렬된 배열 반환
return sortedProducts;
});
};
useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
useEffect(() => {
if (allData.length > 0) {
setLoading(true);
if (title === "all") {
setProducts(allData);
} else {
setProducts(allData.filter((item) => item.category === title));
}
}
}, [allData, title]);
if (!loading) {
return (
<OnlineShopsectionBlock>
<ProductInsert>
<Link to="/productInsert">상품등록</Link>
</ProductInsert>
</OnlineShopsectionBlock>
);
}
return (
<OnlineShopsectionBlock className="row">
<ButtonBlock>
<button onClick={() => sortProduct("name")}>상품명순</button>
<button onClick={() => sortProduct("price")}>가격순</button>
</ButtonBlock>
<UlBlock>
{products.map((item, index) => (
<ListBlock key={index}>
<div className="photo">
<Link to={`/product/${item.id}`} state={{ item: item }}>
<img src={item.photo} alt={item.name} />
</Link>
</div>
<div className="info">
<p>
<a href="#">{item.name}</a>
</p>
<p>{parseInt(item.price).toLocaleString()}¥</p>
</div>
</ListBlock>
))}
</UlBlock>
{user && user.userId === "admin@example.com" && (
<ProductInsert>
<Link to="/productInsert">상품등록</Link>
</ProductInsert>
)}
</OnlineShopsectionBlock>
);
};
export default OnlineShopsection;
sortProduct 코드에서 각 부분을 자세히 설명하겠습니다.
setProducts((products) => { ... }):
- setProducts는 상태 업데이트 함수입니다.
- products는 현재 상태의 상품 배열을 나타냅니다.
콜백 함수 내부에서 상태를 업데이트합니다.
.let sortedProducts = [...products]
- 기존 상품 배열을 복사하여 sortedProducts라는 새로운 배열을 생성합니다.
- 이렇게 하면 기존 상태를 직접 변경하지 않고 새로운 상태를 생성할 수 있습니다.
sortedProducts.sort((a, b) => (a[keyname] < b[keyname] ? -1 : 1)):
- sort() 메서드를 사용하여 배열을 정렬합니다. 정렬 기준은 keyname에 따라 다릅니다.
- 비교 함수 (a, b) => (a[keyname] < b[keyname] ? -1 : 1)은 두 객체의 keyname 속성을 비교하여 정렬 순서를 결정합니다.
- a[keyname] < b[keyname]이 참이면 a가 b보다 앞에 위치합니다 (-1 반환).
- [keyname] >= b[keyname]이면 a가 b보다 뒤에 위치합니다 (1 반환).
return sortedProducts:
- 정렬된 배열을 반환하여 상태를 업데이트합니다.
이번 글에서는 Firebase와 React, Redux를 사용하여 쇼핑몰 상품을 등록하고, Redux를 통해 상품 데이터를 관리하는 방법에 대해 알아봤습니다.