Skip to main content

Command Palette

Search for a command to run...

Key를 활용하여 컴포넌트 상태를 초기화하기

Updated
3 min read

개요

컴포넌트의 상태가 업데이트되어도 컴포넌트 내부에 배치된 컴포넌트의 상태는 그대로 유지되는 경우가 있었다.

예전에는 Props 전달 및 내부에서 useEffect를 통해서 상태를 초기 상태로 업데이트하도록 했는데 useEffect를 남용하게 되면 상태 변화 추적이 어려워지는 문제가 있었다.

그런데 useEffect 외에도 Key를 통해서 컴포넌트 상태를 초기화할 수 있다는 사실을 알게 되었다.

리스트 렌더링

React 프론트엔드에서 목록 형태의 데이터를 렌더링하려면 다음과 같은 방법을 사용할 수 있다.

const Component = () => {
  const { data: feeds } = useQuery({
    queryKey: getFeedsQueryKey(),
    queryFn: getFeedsQueryFn(),
  });

  return (
    <div>
      {feeds.map((feed) => {
        return <FeedView feed={feed} key={feed.id} />;
      })}
    </div>
  );
};

리스트를 map과 같은 메소드로 렌더링할 때 각 항목마다 불변의 키를 부여하면 리액트가 항목을 추적하게 된다.

특히 배열 내 특정 항목이 제거되거나 새로운 항목이 추가되었을 때 Key가 있어야 필요한 컴포넌트만 업데이트되고 DOM 재사용이 가능하다.

Key로 컴포넌트 재배치

Key는 리스트 데이터를 렌더링하는 것 외에도 특정 컴포넌트를 아예 재설정할 때 사용할 수 있다.

예를 들어 아래 코드는 Page 컴포넌트 예시이고 Counter 컴포넌트를 렌더링하고 있다. Counter 컴포넌트는 내부에서 useState로 숫자 상태를 관리하고 있다.

사용자가 입력 필드에 문자열을 입력하고 버튼을 클릭하면 URL 쿼리 파라미터에 기반하여 페이지가 변경된다.

"use client";

import { useRouter, useSearchParams } from "next/navigation";
import {
  ChangeEventHandler,
  FormEventHandler,
  MouseEventHandler,
  useState,
} from "react";

const Counter = () => {
  const [count, setCount] = useState(0);
  const onClick: MouseEventHandler<HTMLButtonElement> = (event) => {
    const action = event.currentTarget.dataset.action;
    switch (action) {
      case "increment":
        setCount((c) => c + 1);
        break;
      case "decrement":
        setCount((c) => c - 1);
        break;
    }
  };
  return (
    <div className="max-w-sm flex gap-4 items-center">
      <p>Count: {count}</p>
      <div className="flex gap-2">
        <button onClick={onClick} data-action="decrement">
          Decrement
        </button>
        <button onClick={onClick} data-action="increment">
          Increment
        </button>
      </div>
    </div>
  );
};

export default function Page() {
  const [keyword, setKeyword] = useState("");
  const searchParams = useSearchParams();
  const router = useRouter();
  const onChange: ChangeEventHandler<HTMLInputElement> = (event) => {
    setKeyword(event.currentTarget.value);
  };
  const onSubmit: FormEventHandler<HTMLFormElement> = (event) => {
    event.preventDefault();
    const newParams = new URLSearchParams(searchParams);
    newParams.set("keyword", keyword);
    router.push(`/feeds?${newParams.toString()}`);
  };
  return (
    <div className="flex flex-col p-4 gap-y-4">
      <Counter />
      <form
        method="GET"
        className="flex flex-col max-w-sm border rounded-sm gap-y-2.5 p-4"
        onSubmit={onSubmit}
      >
        <input
          type="text"
          name="keyword"
          value={keyword}
          onChange={onChange}
          className="border rounded-sm px-2"
        />
        <button>Go</button>
      </form>
    </div>
  );
}

리액트에서 렌더 트리에서 동일한 위치에 있는 컴포넌트는 상태가 보존된다. URL이 변경되어도 UI 상에서 Counter 컴포넌트의 위치는 변경되지 않기 때문에 useState로 관리하는 상태는 그대로 유지된다.

URL 쿼리 파라미터가 변경될 때 Counter 내 상태까지 초기화되어야 한다는 요구사항이 추가되면 다음과 같은 방법을 사용할 수 있다.

const Counter = () => {
  const [count, setCount] = useState(0);
  const searchParams = useSearchParams()

  useEffect(() => {
    setCount(0)
  }, [searchParams])

  /* ... */

  return (
    /* ... */
  )
};

URL 쿼리 파라미터가 변경되면 useEffect를 호출시켜 상태를 0으로 변경되도록 한다.

이 방법은 Counter가 이전 상태로 렌더링한 다음 다시 새로운 값으로 렌더링하기 때문에 비효율적이다. Counter와 다르게 복잡한 상태를 관리하는 컴포넌트의 경우 여러 상태를 직접 초기화하는 과정에서 예상하지 못한 이슈가 발생할 수 있다.

대신 명시적으로 Key를 전달하여 렌더 트리에서 Counter 컴포넌트가 서로 다른 컴포넌트라는 것을 리액트에 전달할 수 있다.

export default function Page() {
  const searchParams = useSearchParams();

  {
    /* ... */
  }

  return (
    <div className="flex flex-col p-4 gap-y-4">
      <Counter key={searchParams.toString()} />
      {/* ... */}
    </div>
  );
}

여기서는 URL 쿼리 파라미터를 문자열화하여 Key로 전달하고 있다. URL 쿼리 파라미터가 변경되면 Counter에 전달되는 Key가 변경된다. 이는 기존 컴포넌트 대신 새로운 컴포넌트가 배치되도록 하며 상태 또한 초기화된다.

URL 쿼리 파라미터로 Key를 관리하는 경우 순서가 변경되어도 다른 키로 인식되어 키를 기반으로 정렬할 필요가 있었다.

More from this blog

AI 에이전트에서 작업 맥락을 관리하기 위해 사용한 방법

AI 에이전트의 불편함 Codex CLI와 같은 AI 코딩 에이전트를 통해서 프로젝트 개발 속도를 높일 수 있다. 그리고 혼자 작업했을 때는 놓칠 수 있는 부분도 발생할 수 있는데 AI 에이전트를 통해 보완할 수 있었다. 컴포넌트에 대해서 접근성 태그 작성 API 호출하는 비동기 코드 작성할 때 에러 처리 그리고 AI 에이전트를 잘 활용하면 불가능하

Mar 8, 202610 min read

지시사항을 분할하여 Codex로 효율적인 작업하기

이슈 Codex와 같은 AI 기반 코드 에이전트는 출력 토큰의 수가 제한되어 있다. 그래서 다양한 작업을 하나의 프롬프트에 전부 몰아서 작성하면 원하는 결과가 나오지 않을 수 있다. Codex와 같은 LLM 기반 서비스는 출력 토큰 수를 초과하게 되면 일부 단계가 누락되거나 특정 요소를 과하게 요약할 수 있고 이는 전체적인 답변 퀄리티를 낮출 수 있기 때문이다. 그래서 다양한 AI 공식문서는 특정 작업을 더 작은 단위로 분할하는 방식을 강조한다...

Nov 3, 20255 min read

내가 AGENTS.md를 작성하는 방법

Codex CLI 요즘 프로젝트 개발에 Codex CLI를 활용하는 이유는 다음과 같다. 특정 프로젝트를 작업할 때 Claude Code는 종종 사용량을 초과한 것과 다르게 Codex CLI는 ChatGPT Plus 요금제만으로도 토큰 사용량 초과 걱정 없이 충분하게 활용할 수 있었다. 타 CLI 기반 AI 개발 도구에 비해서 요구사항을 더 정확하게 구현하고 꼭 필요한 작업만 진행하기 때문에 코드를 검토하는 시간을 줄일 수 있다. 반복적인 코드...

Sep 21, 20254 min read
N

Nowon Lee

22 posts