이번 프로젝트는 물론 사용해보지 않은 것들을 다뤄보면서 배우고자 하긴 하였지만, 이미 자잘하게 배우고 있기에 Redux와 같은 도구는 조금 이후에 다뤄볼 생각이었었다.
express를 연결하고 DB를 연결해 데이터를 활용하도록 작업을 하던 도중
Cannot read properties of undefined (reading 'src')
TypeError: Cannot read properties of undefined (reading 'src')
이런 에러를 접하고 말았다.
그냥 기존의 js파일에 저장되어 있던 데이터들을 불러오는 방식을 단지 DB에서 가져오는 방식으로 바꾸었을 뿐인데, 전혀 상상치도 못한 에러였다.
처음에는 변수명이라던가 뭔가 실수한 부분이 있나 의심하다가 결국 내 생각이 닿은 곳은..
axios.get('http://localhost:8000/api/item')
...
이 부분을 itemSlide.js와 itemDetail.js에서 둘 다 사용하는데, 반복된 사용이 뭔가 에러를 발생시킨게 아닐까 하는 것이었다.
그래서 손대게 된 것이 Redux이다.
(사실 에러의 원인은 그저.. review 컴포넌트에서 프로퍼티의 값을 넘겨 받을때 변수 명을 잘못 설정해서 그리고 렌더링에 대한 이해 부족으로 벌어진..내 n시간)
- Redux
https://memo-code.tistory.com/42
[React] Redux
Redux 상태 관리를 위한 라이브러리. useState와 같은 기능을 제공하나, 해당 컴포넌트에서만 사용되는 useState와 달리 전역적으로 사용이 가능하다. - 설치 npm i redux react-redux 사용하기 위해서 먼저
memo-code.tistory.com
Redux에 대한 자세한 정리는 따로 해두었다.
이번 프로젝트에 사용될 리덕스 구성은 윗 게시물과 동일하다.
이 스토어를 이제 원하는 위치에서 사용하도록 해야한다.
<ItemSlide.js>
import { fetchItems } from '../redux/actions/itemActions';
import { Swiper, SwiperSlide } from 'swiper/react';
...
const ItemSlide = () => {
const dispatch = useDispatch();
const slides = useSelector(state => state.items.items);
useEffect(()=>{
dispatch(fetchItems());
},[dispatch]);
...
{swiper(slides)}
...
}
function swiper(props) {
...
}
export default ItemSlide;
다음과 같이 구성하였다.
여기서는 그냥 불러오고 보여주기만 해도 문제가 없었으나,
<itemDetail.js>
...
const dispatch = useDispatch();
const items = useSelector(state => state.items.items);
const itemdetail = items.find(item => item.id === parseInt(item_id));
useEffect(() => {
dispatch(fetchItems());
}, []);
...
여기에서는 items에서 undefined값이 저장되는 문제가 발생하였다.
렌더링 문제가 발생하였던 것인데, 쉽게 설명하자면,
items의 데이터를 불러오는 동안 이미 렌더링이 종료되어버렸고, 이에 에러메시지가 발생하게 되어 더이상 데이터를 불러오지 못하게 되는 상황이었다.
즉, 에러 메시지가 뜨지 않고 데이터를 가져올 수 있는 시간은 번다면(?) 해결할 수 있다.
...
const dispatch = useDispatch();
const items = useSelector(state => state.items.items);
const itemdetail = items.find(item => item.id === parseInt(item_id));
useEffect(() => {
dispatch(fetchItems());
}, []);
if (!items) { //렌더링 제어.
return <div>Loading...</div>
}
...
따라서 items가 존재하지 않을 경우 loading을 표시하여 에러가 뜨지 않도록하여 데리터 로딩에 시간을 확보하였다.
같은 방법으로 DB에는 qna, review, user 테이블을 만들어 각 데이터에 맞는 페이지를 수정하였다.
<Review.js>
import React, { useEffect } from "react";
import { useDispatch, useSelector } from 'react-redux';
import { fetchReviews } from "../redux/actions/reviewActions";
import WeekItem from "../images/WeekItem";
import "../css/Review.css";
const Review = (props) => {
let item_id = props.item_id;
const dispatch = useDispatch();
const reviewlist = useSelector(state => state.reviews.reviews);
const reviews = reviewlist.filter(review => review.item_id === parseInt(item_id));
useEffect(()=>{
dispatch(fetchReviews());
}, []);
if (!reviews) {
return <div>Loading...</div>
}
return (
...
review 페이지를 수정하면서, 이전의 itemSlide 페이지와는 달리 이미지가 특정 url이 아닌 로컬 파일이었기 때문에 import 하여야하는 문제가 발생했다.
건네받은 src값을 가지고 import 문을 구성할 수 있을까 싶어 import 하지 않는 방법을 또다시 찾아보게 되었는데,
그냥.. src폴더가 아닌 루트 폴더 아래에 있는 public폴더에 넣어두면 import하지 않아도 된다.
<QnA.js>
import React, { useEffect } from "react";
import { useDispatch, useSelector } from 'react-redux';
import { fetchQnas } from "../redux/actions/qnaActions";
import "../css/QnA.css";
const QnA = (props) => {
let item_id = props.item_id;
const dispatch = useDispatch();
const qnalist = useSelector(state => state.qnas.qnas);
const qnas = qnalist.filter(qna => qna.item_id === parseInt(item_id));
useEffect(() => {
dispatch(fetchQnas());
}, []);
return (
...
여기서는 mysql이 boolean 형태를 지원하지 않아 TINYINT를 활용하여야 하는 불편함이 있었다.
DB에서 불러오는 과정에서 SELECT IF(date, 'true', 'false') ... 문을 통해 해결이 가능한 듯 싶으나, 다른 문법과 병행해서 쓰는 법을 몰라 일단 보류하고 0, 1로 활용중이다.
<Login.js>
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from "../redux/actions/userAcions";
import { useNavigate } from 'react-router-dom';
import "../css/Login.css";
const Login = () => {
const [id, setId] = useState('');
const [pw, setPw] = useState('');
const navigate = useNavigate();
const dispatch = useDispatch();
const userlist = useSelector(state => state.users.users);
useEffect(() => {
dispatch(fetchUsers());
}, []);
const checkUser = (e) => {
e.preventDefault();
const state = userlist.find(user => user.user_id === id && user.user_pw === pw);
if(state){
navigate(-1);
}else{
alert('아이디 또는 비밀번호를 확인해주세요.');
}
}
return (
...
전체적으로 날짜 값이 T00:00:00.000Z 이런식으로 나오는데, 이게 ISO 8601?의 날짜 표준 형식이어서 그렇다고 한다.
이 역시도 포맷형식을 통해서 바꿔주던가 하여야하는데, 일단 다른 방법이 있나 찾아보려한다.
구현해야할 게 산더미 같긴한데, 좀 차근차근 하나씩 하려한다. 일단 Read방식은 이제 복붙에 가까우니 로그인 파트 구현을 좀 더 해야할 거 같은데, 당장 오늘 테이블 구성할 때도 유저 토큰값은 어떻게 해야할지, UUID나 sha-256을 배워야하나 고민했는데 로그인 파트를 지금 손대도 괜찮은지 모르겠다. 아니면 당장 구현 못한 카테고리별 파트나 검색 기능을 구현해보던지..
https://memo-code.tistory.com/44
마켓컬리 클론코딩 9 - 회원가입 페이지
회원가입 페이지 다른 페이지를 손보려다가 아무래도 로그인이 된 상태를 기반으로 하는 것이 좋을거같다고 판단했다. 회원정보를 토대로 로그인하는 것은 구현해두었으니, 회원 정보를 만드
memo-code.tistory.com
'기타 > 마켓컬리 클론코딩' 카테고리의 다른 글
[React/Node] 마켓컬리 클론코딩 10 - 로그인/로그아웃 (0) | 2024.05.16 |
---|---|
[React/Node] 마켓컬리 클론코딩 9 - 회원가입 페이지 (0) | 2024.04.23 |
[React/Node] 마켓컬리 클론코딩 7 - node.js express 연결 (0) | 2024.04.02 |
[React/Node] 마켓컬리 클론코딩 6 - 로그인 페이지 (0) | 2024.03.28 |
[React/Node] 마켓컬리 클론코딩 5 - 리뷰 및 문의 페이지 (0) | 2024.03.28 |