TIL

갤러리 상태에는 이미지 대신 번호만 담아요

· 2026-10-14

오늘 배운 것 한 줄

팀 쇼핑몰 프로젝트에서 상품 상세 갤러리를 만들었어요. 만들면서 남은 원칙은 하나예요. 상태에는 화면을 다시 그리는 데 필요한 최소한만 담고, 나머지는 그릴 때 계산한다.

무엇을 만들었나

쇼핑몰 상세 페이지 맨 위의 사진 영역이에요. 왼쪽에 썸네일이 세로로 서 있고, 하나를 누르면 오른쪽 큰 이미지가 바뀌어요. 아직 DB가 연결되지 않아서 사진 대신 번호가 적힌 임시 칸 6개를 넣었어요.

데스크톱 폭에서 2번 썸네일에 검은 테두리가 생기고, 큰 이미지 칸에 ’상품 이미지 2’가 표시된 갤러리 2번 썸네일을 누른 화면. 테두리와 큰 이미지의 번호가 같은 상태 하나에서 나와요.

처음 생각

처음엔 선택된 사진을 통째로 상태에 넣으려고 했어요. 주소, 대체 텍스트, 순서까지 다 들어 있는 객체요. 큰 이미지를 그릴 때 그 객체를 바로 꺼내 쓰면 되니까 편해 보였거든요.

그런데

useState에 넣은 첫 값은 컴포넌트가 처음 그려질 때 한 번만 쓰여요. 나중에 사진 배열이 바뀌어도 상태 안의 객체는 옛날 것 그대로 남아요. 그러면 지금 배열에 없는 사진을 상태가 계속 들고 있게 되죠. 임시 칸이 진짜 상품 이미지로 바뀔 날이 정해져 있으니, 이건 언젠가가 아니라 다음 주의 문제였어요.

그래서 상태에는 번호 하나만 두기로 했어요. 몇 번째가 눌렸는지만요. 큰 이미지는 그릴 때마다 지금 배열에서 그 번호로 꺼내요. 사진의 진짜 출처는 언제나 배열 하나뿐이에요. 데이터가 임시 칸에서 실제 이미지로 바뀌어도 클릭 처리는 손댈 필요가 없어요.

내 예시

// 전: 객체를 상태에 복사한다. photos가 바뀌어도 current는 그대로
const [current, setCurrent] = useState(photos[0]);
<button onClick={() => setCurrent(photo)} />
// 후: 번호만 상태로. 사진은 그릴 때 꺼낸다
'use client';
import { useState } from 'react';

type Photo = { src: string; alt: string };

export default function Gallery({ photos }: { photos: Photo[] }) {
  const [selected, setSelected] = useState(0);
  const current = photos[selected] ?? photos[0]; // 배열이 짧아져도 버틴다

  return (
    <div>
      <img src={current.src} alt={current.alt} />
      <ul className="flex gap-2 overflow-x-auto p-1">
        {photos.map((photo, i) => (
          <li key={photo.src}>
            <button
              aria-label={`${i + 1}번 사진 보기`}
              aria-pressed={i === selected}
              onClick={() => setSelected(i)}
            >
              <img src={photo.src} alt="" />
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

헷갈린 것·함정

번호는 범위를 벗어날 수 있어요. 5번을 보고 있는데 사진이 3장짜리로 바뀌면 photos[4]는 없어요. 번호를 상태로 쓰기로 했다면 꺼낼 때 기본값을 같이 둬야 해요. 위 예시의 ?? photos[0]이 그 자리예요.

'use client'는 import보다 위에 있어야 해요. 코드 리뷰에서 고친 부분이에요. import 아래에 두면 지시어로 인정되지 않아요. 그러면 파일이 서버 컴포넌트로 취급되고, useState를 쓰는 순간 에러가 나요. 이 지시어는 갤러리 파일에만 붙였어요. 페이지에서 클릭이 있는 곳은 여기뿐이니까요.

모바일에서 선택 테두리 윗부분이 잘렸어요. 모바일에서는 썸네일이 큰 이미지 아래에 가로로 놓이고, 넘치면 옆으로 스크롤돼요.

모바일 폭에서 큰 이미지 아래 가로 썸네일 줄 중 4번이 선택됐는데, 검은 테두리의 윗변과 아랫변 일부가 잘려 보이는 화면 4번 썸네일의 테두리 위쪽이 보이지 않아요.

원인은 overflow-x: auto였어요. 가로만 스크롤되게 했는데, 브라우저에서 계산된 값을 보니 세로도 auto가 돼 있었어요. 한쪽 축만 auto로 두고 다른 쪽을 visible로 남길 수는 없거든요. 그래서 상자 밖으로 그려지는 테두리(Tailwind의 ring)가 잘렸어요. 데스크톱에서는 이 속성을 visible로 되돌려서 문제가 없었고요. 예시 코드의 p-1처럼 목록에 안쪽 여백을 주면 테두리가 그려질 자리가 생겨요.

다음에 해 볼 것

팀 프로젝트의 썸네일 목록에 여백을 넣어 잘린 테두리를 고칠 거예요. 그다음엔 페이지를 상품 id가 들어가는 동적 라우트로 옮기고, 임시 칸 대신 진짜 상품 이미지 배열을 갤러리에 넘겨 볼게요.

주제

ReactuseState컴포넌트 설계

배운 개념

React 상태 기반 UI — 추상화·컴포넌트·상태 관리 · 'use client' 경계 — 기본은 서버 컴포넌트다

앤디의 마케팅노트

마케팅을 시스템으로 만드는 과정을 매주 기록해요. 잘 된 것도, 안 된 것도 같이 봐요.

maily에서 구독하기