3 분 소요

스타벅스를 복제해봅시다 ~ 🧑‍💻

Wecode의 두번째 과제 react로 위벅스 만들기

사전작업.

  1. node -v , npm -v 버전정보 확인
  2. npx create-react-app ‘폴더이름작명’
  3. npm start
  4. npm install react-router-dom –save
  5. npm install sass –save
  6. 무거운것들 .gitignore에 추가
여기까지 잘되었다면 이제 시작해봅시다.

👉 [Mission 0] 레이아웃 리액트로 옮기기


JS에서 만든것을 react폴더에 옮기는데 css가 박살나있었다.

login, list, detail 이렇게 3가지의 js파일이 있는데 파일마다 같은 className 때문에 css가 겹치기때문이었다.

각 페이지별로 페이지이름의 가장큰 태그를 감싸주었고 그것을 바탕으로 scss의 nesting기능을 통해 css가 겹치는것을 해결해주었다.

 .login {
    width: 20%;
    height: auto;
    margin: 100px auto;
    border: 3px solid #dddddd;

    .login-logo {
      margin: 10% 25% 5% 25%;
      text-align: center;

      img {
        width: 100%;
        height: 100%;
      }
    }
nesting 맛보기


👉 [Mission 1] 리스트 페이지 목데이터로 map 사용하여 구현하기


{
  "cold": [
    {
      "id": "1",
      "img": "/images/junhyeok/coffee1.png",
      "name": "토피 넛 콜드 브루"
    },
    {
      "id": "2",
      "img": "/images/junhyeok/coffee2.png",
      "name": "나이트로 바닐라 크림"
    },

    ...
    ...
    ...

    ],

    "shot": [
          {
      "id": "1",
      "img": "/images/junhyeok/coffeeshot1.png",
      "name": "아메리카노"
    },
    ...
    ...
    ...
    ]
}
나의 mockData


useEffect(() => {
  fetch('http://localhost:3000/data/junhyeok/data.json')
    .then((res) => res.json())
    .then((res) => setMockdata(res));
}, []);

let [mockdata, setMockdata] = useState([]);

useEffect에 []를 써줌으로써 페이지가 최초랜더링될때 한번 fetch를 통해 해당url로 get요청한다. fetch함수는 Promise를 반환하기에 then을 사용하여 받은 데이터를 setter함수를 통해 mockdata라는 변수에 저장하였다.

{
  mockdata.cold &&
    mockdata.cold.map((e, i) => {
      return <CoffeeCard key={i} item={e} />;
    });
}

아직 완전하게 react의 시스템을 알고있는게 아니라 정확하진않지만 내생각을 써보겠다.

우선 mockdata를 바로 map을 돌리면 에러가 뜰것이다.

이유는 useEffect는 페이지가 랜더링된 후 실행되기 때문이다.

즉, 페이지가 브라우저에 보여진 후 fetch로 데이터를 가져오는 것이다.

그렇기에 mockdata에는 값이 들어있지 않아 undefined에러가 뜰것이다.

따라서 &&를 통해 조건문으로 ‘mockdata.cold에 데이터가 있다면~’을 붙여주어 데이터가 들어오는 턴에 map을 돌릴 수 있도록하자.


👉 [Mission 2] Login | 사용자 입력 데이터 저장


function handleIdInput(event) {
  setid(event.target.value);
  console.log(id);
}

input에 onChange 이벤트의 콜백함수로 handleIdinput을 호출하여 input의 text값을 id라는 state에 저장해준다. 해당함수에서 console을 찍어보면 입력값보다 1글자씩 밀리는 것을 볼 수 있는데 아직 랜더링되기전의 값을 찍고있기때문이다.


👉 [Mission 3] Login | 로그인 버튼 활성화 (validation)


const condition =
  id.includes('@') && pw.length > 7 && num > -1 && eng > -1 && spe > -1;

<button style=>
  로그인
</button>;

나는 button의 style에 삼항연산자를 통해 조건을 만족하였을때 배경색을 변경해주었는데

button의 속성중 하나인 disabled를 통해 true false로 변경해주어도된다.


👉 [Mission 4] 커피 상세 페이지 좋아요 기능 구현하기


let [like, setlike] = useState('🤍');

<span
  className="like"
  onClick={() => {
    like === '🤍' ? setlike('❤️') : setlike('🤍');
  }}
>
  {like}
</span>;


디테일 페이지에 하트를 추가하는 미션은 그냥 state를 변경해주는 기능이므로 설명을 생략하겠다.


👉 [Mission 5] 커피 상세 페이지 리뷰 리뷰 달기 기능 구현하기


let [inputText, setinputText] = useState([]);

let pressEnterEvent = (e) => {
  if (e.keyCode === 13 && e.target.value.length > 0) {
    let copy = [...inputText];
    copy.push(e.target.value);
    setinputText(copy);
    e.target.value = '';
  }
};

function ReviewComment(props) {
  return (
    <div className="commentBox">
      <b>{sessionStorage.getItem('id')}</b> : {props.text}
    </div>
  );
}


input의 onKeyDown이벤트의 콜백함수로 pressEnterEvnet함수를 호출하여

엔터키를 누를때마다 input에 쓰여진 text가 inputText라는 배열의 state에

저장이되고 그 배열을 map을 돌리는데, return 값으로 ReviewComment의 컴포넌트를 주어

배열의 내용이 출력되게하였다.


👉 [Mission 6] 커피 리스트 페이지 커피별 좋아요 기능 구현하기


function CoffeeCard(props) {
  let [like, setlike] = useState('🤍');
  return (
    <div className="add">
      <img src={props.item.img} alt={props.item.name} />
      <div className="name">
        <h4>{props.item.name}</h4>
        <span
          className="like"
          onClick={() => (like === '🤍' ? setlike('❤️') : setlike('🤍'))}
        >
          {like}
        </span>
      </div>
    </div>
  );
}


각각의 상품에다 하트를 만들기 위해선 각 상품의 수에 맞게 state를 생성시켜주어야한다. 따라서 map을 돌릴 컴포넌트에 like라는 state를 추가하여 각각의 하트를 구현해주었다.


👉 [Mission 7, 8] 커피 상세 페이지 리뷰 삭제, 좋아요 기능 구현하기


result1


처음에는 text 배열 state와 그배열을 map돌려 return 값의 컴포넌트의 안에 하트 state를 따로 선언하여 배열의 값을 삭제하면

랜더링이 되어 새롭게 map이 실행되어 컴포넌트안의 하트 state도 지워질 것이라고 생각했다.

하지만 실제로는 위의 사진처럼 되었다.

이유는 하트의 state는 한번 선언되었기에 그값을 set해주지 않는 이상 변하지않기 때문이다.

따라서 아래와 같이 변경해주었다.

//댓글기능
function ReviewComment(props) {
  return (
    <div className="commentBox">
      {/* text배열에서 글받기 */}
      <span className="commentText" onClick={() => spanEvent(props)}>
        <b>{sessionStorage.getItem('id')}</b> : {props.text}
      </span>
      {/* 삭제버튼 */}
      <button className="deleteBtn" onClick={() => deleteEvent(props)}>
        삭제
      </button>
    </div>
  );
}

// 하트이벤트
function spanEvent(props) {
  let copy = JSON.parse(JSON.stringify(props.inputText)); //deepCopy
  copy[props.index][1] === '🤍'
    ? (copy[props.index][1] = '❤️')
    : (copy[props.index][1] = '🤍');
  props.setinputText(copy);
}
// 삭제이벤트
function deleteEvent(props) {
  let deepcopy = JSON.parse(JSON.stringify(props.inputText)); //deepCopy
  deepcopy.splice(props.index, 1); // text 배열요소 삭제
  props.setinputText(deepcopy);
}

text배열에 하트도 함께 포함시켜주어 text배열의 값을 지워주면 하트도 함께 지워줄 수 있도록 변경하였다.

result1


끝-

업데이트: