[NextJS] Trouble Shooting

우지끈·2025년 1월 9일
0
post-thumbnail

퍼널을 구현하면서 각 단계를 하나의 공통 컴포넌트로 합치는 작업 중 문제가 발생했다. 원래 개별적으로 구현되어 있었던 Question1, Question2와 같은 단계를 Question.tsx, AnswerList.tsx 로 통합하고, map을 통해 렌더링하도록 수정했지만, 렌더링이 아예 되지 않는 상황이 벌어진 것이다.


트러블슈팅 과정1

map으로 처리한 부분에서 다음과 같은 에러가 발생했다.

funnel 부분 코드 작성에 많은 시간을 들인 터라, 급한 마음에 에러를 해결하려고 타입 단언을 남발하고,(안 좋은거 알지만...) 이미 꼼꼼하게 선언했던 타입을 유연하게 변경하는 등 온갖 시도를 다 해봤다.

하지만 그렇게 정신 없이 뜯어 고치다보니 점점 코드가 복잡해졌고, 문득 이러다가 돌이킬 수 없겠다(?)라는 생각이 들어 코드를 다시 원점으로 돌리고 침착하게 하나씩 콘솔을 찍어가면서 문제 원인을 파악해보고자 했다.


트러블슈팅 과정2

첫 번째 단서

에러 메세지에 StepProps가 있었기에 StepProps를 선언했던useFunnel.tsx 파일로 이동했다.

// src/app/recommend/_hooks/useFunnel.tsx

import { useSearchParams } from "next/navigation";
import { useEffect, useState, type ReactElement, type ReactNode } from "react";

export type StepProps = {
  name: string;
  children: ReactNode;
};

type FunnelProps = {
  children: Array<ReactElement<StepProps>>;
};

export const useFunnel = (initialStep: string) => {
  const searchParams = useSearchParams();
  // 초기 상태 설정
  const [currentStep, setCurrentStep] = useState<string>(
    () => searchParams.get("step") || initialStep,
  );

  // 상태와 url 동기화
  useEffect(() => {
    const stepParam = searchParams.get("step");
    if (stepParam) {
      setCurrentStep(stepParam);
    } else {
      window.history.replaceState(null, "", `?step=${currentStep}`);
    }
  }, [searchParams]);

  const Step = ({ name, children }: StepProps): ReactElement => {
    return <>{children}</>;
  };

  const Funnel = ({ children }: FunnelProps) => {
    const steps = children.filter((child) => child.type === Step);
    const activeStep = steps.find((child) => child.props.name === currentStep);
    return activeStep || null;
  };


  const updateStep = (step: string): void => {
    setCurrentStep(step);
    window.history.pushState(null, "", `?step=${step}`);
  };

  const next = (nextStep: string): void => {
    updateStep(nextStep);
  };

  const prev = (prevStep: string): void => {
    updateStep(prevStep);
  };

  return { Funnel, Step, next, prev };
};

두 번째 단서
dev 모드에서 실행했을 때, URL에 ?step=answer1과 같은 쿼리 파라미터는 정상적으로 반영되고 있었다.

따라서 그 외의 부분이며 StepProps를 사용하고 있는 아주 수상한 Funnel 부분을 살펴보게 되었다.

const Funnel = ({ children }: FunnelProps) => {
  const steps = children.filter((child) => child.type === Step);
  const activeStep = steps.find((child) => child.props.name === currentStep);
  console.log(currentStep); // 정상 출력: answer1
  console.log(activeStep); // undefined 출력
  return activeStep || null;
};

콘솔을 찍어보니 currentStep은 정상적으로 출력되지만, activeStepundefined로 출력되는 문제를 발견할 수 있었다.

그에 따라 find 메서드가 제대로 동작하지 못하고 있다고 판단해 children을 출력해보았다.

Children [
  [
    { type: [Function: Step], key: 'answer1', ... },
    { type: [Function: Step], key: 'answer2', ... },
    { type: [Function: Step], key: 'answer3', ... },
    { type: [Function: Step], key: 'answer4', ... }
  ],
  { type: [Function: Step], key: null, props: { name: 'result', ... } }
]

!!!!
평평한 배열로 출력되어야 할children이 중첩 배열 형태로 출력되었다.
배열의 첫 번째 요소가 중첩 배열로 감싸져 있었기에 메소드가 제대로 동작하지 않았던 것이다.


해결 방법

따라서 다시 map 메소드를 사용한 부분으로 돌아가 children을 다음과 같이 평탄화해서 중첩 배열을 제거했다.

<Funnel>
  {[...questions.map((q, index) => (
    <Step key={q.id} name={q.id}>
      <Question
        question={q.question}
        selectedAnswer={answerData[q.id]}
        answerItems={q.answers}
        onNext={(data) => handleNext(data, steps[index + 1])}
        onPrev={(data) => handlePrev(data, steps[index - 1])}
        fieldKey={q.id}
      />
    </Step>
  )),
  <Step name="result">
    <Result answerData={answerData} />
  </Step>]}
</Funnel>

이렇게 배열을 펼쳐주니 바로 문제가 해결됐다.


느낀 점

코드가 의도치 않게 동작하지 않을 때, 급하게 비권장 방식으로 수정하기보단 문제의 근본적인 원인을 찾기 위한 디버깅 과정이 중요하다는 걸 다시 한 번 느낄 수 있었다. 특히 중첩 배열이라는 단순한 원인을 해결하고나니, 처음 시도했던 타입 단언이나 비효율적인 수정 없이도 코드는 정상적으로 동작했다;;

많은 공을 들인 코드든, 급한 상황이든 항상 침착하게 하나씩 디버깅하는 습관을 들여야겠다고 생각했다...

0개의 댓글

관련 채용 정보