[주문앱 2탄] 카테고리 만들기

비얌·2023년 4월 23일
1
post-thumbnail

🧹 개요

저번 포스팅 [주문앱 1탄] UI 만들기에서는 컴포넌트를 분리하고 기본적인 UI를 만들었다.

하지만 아직 만들지 않은 것이 있는데, 바로 카테고리이다.

아래의 와이어프레임을 보면 생과일, 견과류/건과일, 수제청/시럽 , 시리얼, 케익으로 이루어진 카테고리가 있다. 생과일 카테고리에 무화과, 사과, 청포도, 딸기 등의 생과일 목록이 있듯이 견과류/건과일을 누르면 아몬드, 호두, 마카다미아, 건무화과 등의 목록이 나와야 한다.

이번 포스팅에서는 이 카테고리를 만들어볼 것이다.



✨ 결과 미리보기

최종적으로 완성된 결과는 아래와 같다!



🛫 카테고리 기능 구현하기

더미 데이터 수정하기

현재 단계에서는 서버와의 통신 없이 각 토핑 종류에 따라 하드코딩된 데이터를 이용하기로 했다.

기존에는 이렇게 DUMMY_TOPPINGS만을 만들었었다. 여기에는 그릭요거트라는 하나의 카테고리에 들어가는 재료들만 있었다. 하지만 여러개의 카테고리를 만들고 그 안에 각기 다른 종류의 토핑들이 들어가야 하므로, DUMMY_CATEGORIES라는 데이터를 만들기로 했다.

DUMMY_TOPPINGS: [
      {
        id: 't1',
        name: '무가당 그릭요거트(100g)',
        description: '최상급 우유와 유산균 외의 첨가물이 전혀 없는 그릭요거트',
        price: 3500,
      },
      {
        id: 't2',
        name: '망고 그릭요거트(100g)',
        description: '망고의 달콤함과 풍미를 담은 망고 그릭요거트',
        price: 4500,
      },
      {
        id: 't3',
        name: '딸기 그릭요거트(100g)',
        description: '설향딸기의 달콤함을 담은 딸기 그릭요거트',
        price: 4500,
      },
      {
        id: '4',
        name: '황치즈 그릭요거트(100g)',
        description: '세가지 치즈의 깊은 맛을 담은 황치즈 그릭요거트',
        price: 5000,
      },
    ]

DUMMY_CATEGORIES데이터는 아래와 같다. 위에 만든 DUMMY_TOPPINGSDUMMY_CATEGORIES 안에 넣었다.

그릭요거트, 그래놀라, 수제청&시럽, 시리얼라는 카테고리를 만들고 그 안에 각기 다른 DUMMY_TOPPINGS를 넣었다.

const DUMMY_CATEGORIES = [
  {
    name: '그릭요거트',
    id: 'c1',
    DUMMY_TOPPINGS: [
      {
        id: 't1',
        name: '무가당 그릭요거트(100g)',
        description: '최상급 우유와 유산균 외의 첨가물이 전혀 없는 그릭요거트',
        price: 3500,
      },
      {
        id: 't2',
        name: '망고 그릭요거트(100g)',
        description: '망고의 달콤함과 풍미를 담은 망고 그릭요거트',
        price: 4500,
      },
      {
        id: 't3',
        name: '딸기 그릭요거트(100g)',
        description: '설향딸기의 달콤함을 담은 딸기 그릭요거트',
        price: 4500,
      },
      {
        id: '4',
        name: '황치즈 그릭요거트(100g)',
        description: '세가지 치즈의 깊은 맛을 담은 황치즈 그릭요거트',
        price: 5000,
      },
    ]
  },
  {
    name: '그래놀라',
    id: 'c2',
    DUMMY_TOPPINGS: [
      {
        id: 't1',
        name: '카카오 그래놀라',
        description: '수제로 만든 달콤 쌉싸름한 카카오 그래놀라 한스쿱',
        price: 1500,
      },
      {
        id: 't2',
        name: '얼그레이 그래놀라',
        description: '직접 냉침한 밀크티로 만든 얼그레이 그래놀라 한스쿱',
        price: 1500,
      },
      {
        id: 't3',
        name: '바닐라 그래놀라',
        description: '바닐라빈을 듬뿍 넣은 수제 바닐라 그래놀라 한스쿱',
        price: 1500,
      },
      {
        id: 't4',
        name: '코코넛 그래놀라',
        description: '코코넛청크를 듬뿍 넣은 수제 그래놀라 한스쿱',
        price: 1500,
      },
    ]
  },
  {
    name: '수제청&시럽',
    id: 'c3',
    DUMMY_TOPPINGS: [
      {
        id: 't1',
        name: '딸기청',
        description: '새콤달콤한 설향딸기로 만든 수제 딸기청',
        price: 2000,
      },
      {
        id: 't2',
        name: '블루베리청',
        description: '싱싱한 블루베리로 만든 수제 블루베리청',
        price: 2000,
      },
      {
        id: 't3',
        name: '벌집꿀',
        description: '산지직송한 달콤한 벌집꿀 30g',
        price: 3500,
      },
      {
        id: '4',
        name: '연유',
        description: '달달한 그릭요거트를 원하신다면 최고의 선택',
        price: 1000,
      },
    ]
  },
  {
    name: '시리얼',
    id: 'c4',
    DUMMY_TOPPINGS: [
      {
        id: 't1',
        name: '초코칩',
        description: '인기만점 작은 물방울 모양의 초코칩',
        price: 800,
      },
      {
        id: 't2',
        name: '드라이 마시멜로',
        description: '큰 마시멜로가 부담스러울 때 딱 좋은 미니미 마시멜로',
        price: 1000,
      },
      {
        id: 't3',
        name: '후르츠링',
        description: '새콤달콤한 무지개색의 후르츠링 ',
        price: 800,
      },
      {
        id: '4',
        name: '초코 프레첼',
        description: '묵직하고 달콤한 허쉬의 초코 프레첼',
        price: 1500,
      },
    ]
  },
]

ToppingsCategory 컴포넌트 만들기

ToppingsCategory라는 카테고리 컴포넌트를 만들었다. Toppings 컴포넌트에 더미 데이터가 있기 때문에, 거기서 prop으로 데이터를 받아 출력할 것이다. 카테고리의 이름(그릭요거트, 그래놀라, 수제청&시럽, 시리얼)을 props.DUMMY_CATEGORIES에서 map()으로 하나씩 꺼낸다.

// 📃 ToppingsCategory.jsx
import React from 'react';
import classes from './ToppingsCategory';

const ToppingsCategory = (props) => {
  const selectHandler = (id) => {
    props.onSelect(id);
  }

  return (
    props.DUMMY_CATEGORIES.map(category => 
        <li
          key={category.id}
          onClick={() => selectHandler(category.id)}
      	>
        	{category.name}
      	</li>
    )
  );
};

export default ToppingsCategory;

State(선택된 카테고리) 추가하기

카테고리에는 선택할 수 있는 네가지 종류의 토핑들이 있다.(그릭요거트, 그래놀라, 수제청&시럽, 시리얼) 이중에서 그릭요거트를 선택하면 여러 그릭요거트가 보여야 하고, 시리얼을 선택하면 여러 시리얼이 보여야 한다.

그렇다면 이건 어떻게 구현할 수 있을까 생각하다가, 선택된 카테고리를 의미하는 State인 selectedCategory를 만들기로 했다. 이 State는 각 카테고리의 id를 의미한다.

만약 selectedCategoryc1이면 그릭요거트에 해당하는 재료들을 보여준다. 만약 c2이면 그래놀라에 해당하는 재료들을 보여준다.

selectedCategory는 ToppingsCategory 컴포넌트에서도 쓰이지만, AvailableToppings 컴포넌트에서도 쓰인다. (AvailableToppings 컴포넌트: 선택된 카테고리에 있는 재료들을 보여주는 곳) 그리고 ToppingsCategory 컴포넌트와 AvailableToppings 컴포넌트는 모두 Toppings 컴포넌트에서 임포트된다. 그래서 selectedCategory라는 State 또한 Toppings 컴포넌트에 선언했다.


데이터 끌어올리기

ToppingsCategory 컴포넌트에서 카테고리를 선택할 수 있다. 그런데, 선택된 카테고리에 관한 정보는 상위 컴포넌트인 Toppings에서 필요하다. 왜냐하면, 그곳에 있는 DUMMY_CATEGORIES에서 데이터를 필터링하여 AvailableToppings라는 컴포넌트에 전달해야 하기 때문이다.

그렇다면 ToppingsCategory 컴포넌트에서 선택된 카테고리에 대한 데이터를 Toppings로 끌어올려야 한다.

먼저 ToppingsCategory 컴포넌트에서 카테고리가 선택되었을 때의 상황을 살펴보자.

// 📃 ToppingsCategory.jsx
import React from 'react';
import classes from './ToppingsCategory';

const ToppingsCategory = (props) => {
  const selectHandler = (id) => {
    props.onSelect(id);
  }

  return (
    props.DUMMY_CATEGORIES.map(category => 
      <li
        key={category.id}
        onClick={() => selectHandler(category.id)}
      >
        {category.name}
      </li>
    )
  );
};

export default ToppingsCategory;
  • prop으로 받은 DUMMY_CATEGORIESmap()을 이용하여 <li> 안에 펼친다. 그리고 이 중 하나의 카테고리가 클릭되면 selectHandler 함수에 해당 카테고리의 id를 넘긴다.
  • selectHandler 함수는 선택한 카테고리의 id를 상위 컴포넌트로 끌어올리기 위해 존재한다.
  • prop으로 받은 onSelect라는 함수에 선택된 카테고리의 id를 인자로 전달한다.

Toppings 컴포넌트의 onSelect로 넘어온 id로 selectedCategory를 업데이트한다. 이렇게 해서 ToppingsCategory 컴포넌트에서 선택된 카테고리를 Toppings에서 쓸 수 있게 되었다.

// 📃 Toppings.jsx
const Toppings = () => {
  // 생략
  const onSelect = (id) => {
    setSelectedCategory(id);
  }
  return (
    // 생략
      <ToppingsCategory
        DUMMY_CATEGORIES={DUMMY_CATEGORIES}
        onSelect={onSelect}
      />
    )
}

filter()로 데이터 필터링하기

선택된 카테고리의 id를 끌어올려 가져온 이유는 데이터를 필터링하기 위해서이다.

DUMMY_CATEGORIES에서 선택된 id를 갖고 있는 데이터만 선택하고, 그것을 toppingsInSelectedCategory라고 선언하려고 한다.

만약 그래놀라 카테고리의 id가 선택되었다면 toppingsInSelectedCategory는 그래놀라 재료들을 담은 객체일 것이다.

그리고 이들은 AvailableToppings 컴포넌트에 prop으로 넘겨져서 선택된 카테고리의 재료들을 화면에 보여주게 된다.

// 📃 Toppings.jsx
const Toppings = () => {
  // 생략
  const onSelect = (id) => {
    setSelectedCategory(id);
  }

  const toppingsInSelectedCategory = DUMMY_CATEGORIES.filter(category => {
    return category.id === selectedCategory;
  });

return (
  // 생략
  <AvailableToppings 
        toppingsInSelectedCategory={toppingsInSelectedCategory} 
      />
  )
}

export default Toppings;
  • 선택된 카테고리의 id를 setSelectedCategory에 인자로 넣어 selectedCategory를 업데이트한다.
  • filter() 함수를 이용하여 선택된 카테고리에 해당하는 재료들을 toppingsInSelectedCategory라고 선언한다
  • AvailableToppings 컴포넌트에 toppingsInSelectedCategory를 prop으로 넘긴다

필터링된 데이터 보여주기

이제 필터링된 데이터를 받은 AvailableToppings 컴포넌트에 가보자.

prop으로 받은 toppingsInSelectedCategory에서 DUMMY_TOPPINGSmap()으로 펼친 후, 거기서 각각 id와 name, description, price를 ToppingItem으로 넘겨주면 된다. 그럼 ToppingItem에서 이 정보를 활용하여 화면에 보여줄 것이다.

// 📃 AvailableToppings.jsx
import React from 'react';
import Card from '../UI/Card';
import ToppingItem from './ToppingItem/ToppingItem';
import classes from './AvailableToppings.module.css';

const AvailableToppings = (props) => {
    const toppingsList = 
          props.toppingsInSelectedCategory[0].DUMMY_TOPPINGS.map(
            topping => 
              <ToppingItem
                id={topping.id}
                key={topping.id}
                name={topping.name}
                description={topping.description}
                price={topping.price}
              />
  		)
                                                                                
  return (
    <section className={classes.toppings}>
      <Card>
        {toppingsList}
      </Card>
    </section>
  );
};

export default AvailableToppings;

참고로 prop으로 받은 toppingsInSelectedCategory는 아래와 같다.

{
    name: '그릭요거트',
    id: 'c1',
    DUMMY_TOPPINGS: [
      {
        id: 't1',
        name: '무가당 그릭요거트(100g)',
        description: '최상급 우유와 유산균 외의 첨가물이 전혀 없는 그릭요거트',
        price: 3500,
      },
      {
        id: 't2',
        name: '망고 그릭요거트(100g)',
        description: '망고의 달콤함과 풍미를 담은 망고 그릭요거트',
        price: 4500,
      },
      {
        id: 't3',
        name: '딸기 그릭요거트(100g)',
        description: '설향딸기의 달콤함을 담은 딸기 그릭요거트',
        price: 4500,
      },
      {
        id: '4',
        name: '황치즈 그릭요거트(100g)',
        description: '세가지 치즈의 깊은 맛을 담은 황치즈 그릭요거트',
        price: 5000,
      },
    ]
  },
}

💥 오류 해결 - 데이터 선택

<오류>

props.toppingsInSelectedCategory.DUMMY_TOPPINGS.map(() => {})

<정답>

props.toppingsInSelectedCategory[0].DUMMY_TOPPINGS.map(() => {})

처음에 첫번째처럼 작성해서 오류가 났었다. 그런데 도무지 이유를 모르겠어서 많이 헤맸는데, toppingsInSelectedCategory가 아니라 toppingsInSelectedCategory[0]라고 수정하여 해결했다.

왜 이렇게 해야 하는 걸까? 콘솔을 찍어서 확인해봤다.


props.toppingsInSelectedCategory을 콘솔로 찍어봤을 때, 배열 안에 객체가 들어있다고 나온다.

그리고 배열의 0번째 인덱스에 DUMMY_TOPPINGS가 있다고 나온다. 즉, toppingsInSelectedCategory가 객체가 아니라 배열 안에 있는 객체여서 0번째 인덱스를 선택해야 정확히 DUMMY_TOPPINGS를 선택할 수 있는 것이다.

console.log(props.toppingsInSelectedCategory) // [{...}]


중간 결과

현재 기능 구현은 완료됐다. 카테고리를 선택하면 해당하는 카테고리에 있는 재료들이 화면에 표시된다.


🛫 카테고리 UI 만들기

고민

이제 카테고리의 UI를 보기 좋게 꾸며보기로 했다. 가장 먼저 든 생각은, 어떻게 아래처럼 카테고리와 재료들이 자연스럽게 이어지게 만드냐는 것이었다.

아래처럼 AvailableToppings 컴포넌트 위에 ToppingsCategory 컴포넌트를 쓰고 css로 디자인을 변경하면 되나? 일단 이렇게 해보기로 했다.

<ToppingsCategory />
<AvailableToppings />

Card로 감싸기

원래는 AvailableToppings 컴포넌트 안에서 AvailableToppings의 내용을 Card로 감쌌었는데, 이 Card 컴포넌트를 꺼내서 ToppingsCategory 컴포넌트와 AvailableToppings 컴포넌트를 감싸도록 했다.

// 📃 Toppings.jsx
<Card>
  <ul>
    <ToppingsCategory
      DUMMY_CATEGORIES={DUMMY_CATEGORIES}
      onSelect={onSelect}
      />
  </ul>
  <AvailableToppings 
    toppingsInSelectedCategory={toppingsInSelectedCategory} 
    />
</Card>

이렇게 해서 Card 컴포넌트 안에 ToppingsCategory와 AvailableToppings 컴포넌트가 들어있는 것을 확인했다.


나머지 CSS 작성하기

카테고리의 종류가 잘 기능하도록 만들었으므로, 여기에 추가적인 CSS를 적용하여 UI를 완성했다.



✨ 결과

카테고리가 완성되었다!! 아직 부족한 점이 많지만, 일단 다음 기능을 구현하기 위해 CSS는 여기서 멈추기로 했다.



🔮 개선하고 싶은 부분

  • 와이어프레임으로 계획했던 것처럼 어떤 카테고리를 선택하면 그 카테고리가 있는 칸의 색이 바뀌었으면 좋겠다.

  • 화면의 너비를 줄이면 글자 등이 레이아웃을 무시하고 벗어나는데, 이를 해결하고 싶다.



🐹 회고

카테고리를 처음 만들어보기도 하고, 강의에서 배운 적도 없어서 시작할 때 많이 막막했다.

하지만 계획과 비슷한 카테고리를 구현하게 되어 뿌듯했다!

profile
🐹강화하고 싶은 기억을 기록하고 공유하자🐹

0개의 댓글