Recent Posts
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
관리 메뉴

개발자 박가나

[241107 TIL] 본캠프 27일차 ('포켓몬 도감 만들기' 프로젝트 2일차) 본문

내일배움캠프

[241107 TIL] 본캠프 27일차 ('포켓몬 도감 만들기' 프로젝트 2일차)

gnchoco97 2024. 11. 7. 20:00

  📌 오늘의 TIL
  • [포켓몬 도감] 프로젝트

 

 

 [포켓몬 도감] 프로젝트 

기능 구현. 포켓몬 상세 정보 페이지

선택한 포켓몬의 상세 정보를 가져와서 화면에 보여준다.

 

포켓몬 선택 시 해당 포켓몬의 id 값을 router로 넘겨주고 path param을 이용해서 넘겨받은 id 값에 해당하는 포켓몬의 정보를 읽어온다.

/* Detail.jsx */

import { useParams } from 'react-router-dom';
import MOCK_DATA from '../Data';
import { useEffect, useState } from 'react';

export default function Detail() {
    const param = useParams();

    const [pokemon, setPokemon] = useState();

    useEffect(() => {
        setPokemon(MOCK_DATA.find((item) => item.id === Number(param.id)));
    }, [param.id]);

    return (
        <>
            {pokemon && (
                <Wrap>
                    <Character src={pokemon.img_url} alt="character" />
                    <Name>{pokemon.korean_name}</Name>
                    <Description>
                        타입 :{' '}
                        {pokemon.types.map((type, index) => {
                            return index < pokemon.types.length - 1 ? `${type}, ` : type;
                        })}
                    </Description>
                    <Description>{pokemon.description}</Description>
                    <Button label="뒤로 가기" background="black" handleClick={() => navigate('/dex')} />
                </Wrap>
            )}
        </>
    );
}

 

기능 구현. 나만의 포켓몬 추가 및 삭제

특정 포켓몬을 나만의 포켓몬에 추가 및 삭제한다.

 

중복되지 않게, 최대 6개까지 등록 가능하게 제한한다.

/* Dex.jsx */

import Dashboard from '../components/Dashboard';
import PokemonList from '../components/PokemonList';
import MOCK_DATA from '../Data';
import { useState } from 'react';

export default function Dex() {
    const [myPokemons, setMyPokemons] = useState([]);

    /* 나만의 포켓몬 추가 이벤트 */
    const handleAdd = (data) => {
        // 이미 선택된 포켓몬인 경우
        if (myPokemons.find((pokemon) => pokemon.id === data.id)) {
            window.alert('이미 선택된 포켓몬입니다.');
        }
        // 이미 6개의 포켓몬이 선택된 경우
        else if (myPokemons.length === 6) {
            window.alert('더 이상 선택할 수 없습니다.');
        } else {
            setMyPokemons([...myPokemons, data]);
        }
    };
    
    /* 나만의 포켓몬 삭제 이벤트 */
    const handleDelete = (data) => {
        setMyPokemons([...myPokemons.filter((pokemon) => pokemon.id !== data.id)]);
    };

    return (
        <Wrap>
            <Dashboard myPokemons={myPokemons} handleDelete={handleDelete} />
            <PokemonList pokemons={MOCK_DATA} handleAdd={handleAdd} />
        </Wrap>
    );
}
/* Dashboard.jsx */

import PokemonBall from './PokemonBall';
import PokemonCard from './PokemonCard';

export default function Dashboard({ myPokemons, handleDelete }) {
    return (
        <Container>
            <Title>나만의 포켓몬</Title>
            <BallContainer>
                {myPokemons.map((pokemon) => {
                    return <PokemonCard key={pokemon.id} type={'my'} pokemon={pokemon} handleDelete={handleDelete} />;
                })}
                {[...Array(6 - myPokemons.length)].map(() => {
                    return <PokemonBall key={index} />;
                })}
            </BallContainer>
        </Container>
    );
}
/* PokemonCard.jsx */

export default function PokemonCard({ type = 'list', pokemon, handleAdd, handleDelete }) {
    /* 카드 내 버튼 클릭 이벤트 */
    const handleClick = (e) => {
        // 이벤트 버블링 방지
        e.stopPropagation();

        type === 'list' ? handleAdd(pokemon) : handleDelete(pokemon);
    };

    return (
        <Container onClick={() => navigate(`/detail/${pokemon.id}`)}>
            <Character src={pokemon.img_url} />
            <Name>{pokemon.korean_name}</Name>
            <Description>No. {String(pokemon.id).padStart(3, '0')}</Description>
            <Button type="sub" bgcolor="red" label={type === 'list' ? '추가' : '삭제'} handleClick={handleClick} />
        </Container>
    );
}

 

react-toastify 적용

단순히 window alert이 아니라 react-toastify를 적용시켜 줌으로써 UI를 보완한다.

/* App.jsx */

import styled from 'styled-components';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

const Toast = styled(ToastContainer)`
    .Toastify__toast {
        background-color: #ffbe57;
        border-radius: 8px;
        font-size: 16px;
        font-weight: 700;
        color: #000000;
        text-align: center;
        padding: 10px 20px;
    }
`;

function App() {
    return (
        <>
            <Toast position="top-center" autoClose={2000} closeButton={false} hideProgressBar />
            <Router />
        </>
    );
}

export default App;
/* Dex.jsx */

/* 나만의 포켓몬 추가 이벤트 */
const handleAdd = (data) => {
    // 이미 선택된 포켓몬인 경우
    if (myPokemons.find((pokemon) => pokemon.id === data.id)) {
        toast('이미 선택된 포켓몬입니다.');
    }
    // 이미 6개의 포켓몬이 선택된 경우
    else if (myPokemons.length === 6) {
        toast('더 이상 선택할 수 없습니다.');
    } else {
        setMyPokemons([...myPokemons, data]);
    }
};

 

warning 해결

console 창에 다음과 같은 warning이 떠있었다. error가 아닌 warning이었기 때문에 로직 수행에는 문제가 없었지만 그래도 해결해보자는 생각이 들었다.

 

해석을 해보면 개발자의 의도는 styled-components에 props로 넘겨주는 것이지만 DOM의 입장에서는 attribute로 받아들일 수 있기 때문에 구분이 필요하다는 것이었다.

 

props로 사용할 목적인 경우 변수명 앞에 $를 붙여주는 방법으로 해결 가능하다.

/* Button.jsx */

export default function Button({ type = 'main', bgcolor, label, handleClick }) {
    return (
        <Container type={type} $bgcolor={bgcolor} onClick={handleClick}>
            {label}
        </Container>
    );
}