yamoo9 / likelion-FEQA

질문/답변 — 프론트엔드 스쿨, 멋사
29 stars 9 forks source link

[LAB-5] Suspense delay 이슈 및 상태 에러 문제 #206

Closed seoohyeon closed 1 year ago

seoohyeon commented 1 year ago

질문 작성자

김서현

문제 상황

질문 1. 어제 Suspense 이슈답변해주신거처럼 했는데 적용이 잘됩니다. 근데, delay시간을 주고싶어 아래와 같이 코드를 짰습니다. const ShowCard = React.lazy(async () => { await new Promise((resolve) => setTimeout(resolve, 1000)); return import('./ShowCard'); });

제가 원하는 것은 데이터를 받아오는데에 성공(resolve)해도, 무조건 10초를 로딩시키고 싶은데 화면은 보니 resolve되면 10초가 안되어도 무조건 랜더링시켜주는 것 같았습니다. 해결방법이 궁금합니다.

질문 2. Mar-22-2023_00-56-52

카드UI를 클릭하면 해당 에러가 발생하는데, 제가 해석한 바로는 같은 상태(atom)를
두개의 컴포넌트(MapReading.tsx, MapContainer.tsx)에 모두 불러와 사용해서 생겨난 이슈 같습니다.
해결방법이 궁금합니다. image image

질문 3. image <MapReading mapPosition={value.mapData} /> 이부분을 원래는 단순히 setState(value.mapData)를 이용해 값을 가져오고 싶었는데
해당 구문 안은 JSX문법만 된다고 해서 컴포넌트를 만들어 props로 넘겨주는 방법으로 구현하였습니다.
이 방법이 맞는지, 이러한 방법 외에는 없는지 궁금합니다.

프로젝트 저장소 URL

https://github.com/React-Project-lab5/React-Project-lab5
develop브랜치

환경 정보

yamoo9 commented 1 year ago

문제 파악 1

제가 원하는 것은 데이터를 받아오는데에 성공(resolve)해도, 무조건 10초를 로딩시키고 싶은데 화면은 보니 resolve되면 10초가 안되어도 무조건 랜더링시켜주는 것 같았습니다. 해결방법이 궁금합니다.

왜 무조건 10초를 로딩 시켜야 하는 지 이유는 공감이 안되네요. 매번 요청할 때마다 10초를 사용자가 기다려야 하는 이유가 있나요? 😳

하지만 포함된 질문 코드를 보면 10초가 아니라, 1초(1000ms)로 보입니다. 🤔

const ShowCard = React.lazy(
  async () => { 
    await new Promise((resolve) => setTimeout(resolve, 1000)); 
    return import('./ShowCard'); 
  }
);

문제 해결 1

특정 시간 소유 후, 컴포넌트 로드를 지연 처리하기 위한 lazyMinLoadTime 유틸리티 함수를 작성합니다.(참고)

import { ComponentType, lazy } from 'react';

export const lazyMinLoadTime = <T extends ComponentType<unknown>>(
  factory: () => Promise<{ default: T }>,
  minLoadTimeMs = 2000
) => {
  return lazy(() =>
    Promise.all([
      factory(),
      new Promise((resolve) => setTimeout(resolve, minLoadTimeMs)),
    ]).then(([moduleExports]) => moduleExports)
  );
};

그리고 지연 처리할 컴포넌트를 아래와 같이 작성해 사용합니다.

const ShowCard = lazyMinLoadTime(() => import('./ShowCard'), 1000);

사용자가 Card 컴포넌트를 클릭하면 모달 다이얼로그가 바로 표시된 후, ShowCard 컴포넌트가 1초 지연된 이후 호출되어 표시됩니다.

문제 파악 2

질문의 경고 메시지를 해석하면 다음과 같습니다.

⚠️ 경고 MapReading 컴포넌트를 렌더링하는 동안 MapContainer 컴포넌트를 업데이트할 수 없습니다. MapReading 내에서 잘못된 setState() 호출을 찾으려면 setstate-in-render을 참고하세요.

문제 해결 2

MapReading 파일 내부에 작성된 상태 업데이트 함수는 컴포넌트가 실제 렌더링 된 이후 실행되어야 하는데 바로 실행되도록 코드를 작성했기 때문에 React가 경고한 것입니다. 이 문제는 useLayoutEffect(또는 useEffect) 훅의 이펙트 콜백 함수 내에서 상태 업데이트를 시도하면 해결됩니다.

export function MapReading({ mapPosition }: Props) {
  console.log('MapReading파일', mapPosition);

  const setMapData = useSetRecoilState(readingMap);

  useLayoutEffect(() => {
    setMapData(mapPosition);
  }, [mapPosition, setMapData]);

  return <></>;
}

이제 Card를 클릭해도 더 이상 경고 문구가 Console 패널에 표시되지 않습니다.

문제 파악 3

<MapReading mapPosition={value.mapData} /> 이부분을 원래는 단순히 setState(value.mapData)를 이용해 값을 가져오고 싶었는데 해당 구문 안은 JSX문법만 된다고 해서 컴포넌트를 만들어 props로 넘겨주는 방법으로 구현하였습니다. 이 방법이 맞는지, 이러한 방법 외에는 없는지 궁금합니다.

작성된 질문을 보면 setState는 "쓰기(write)"인데 값을 "읽기(read)"하고 싶다는 질문은 모순(矛盾)이므로 무엇을 말하고 싶은지 파악하기 어렵습니다. 하지만 질문 맥락을 추론해보면 아마도... MapReading 컴포넌트를 사용하지 않고 지도 위치 정보를 사용하고 싶다고 이해했습니다.

별도로 컴포넌트를 사용하지 않아도 ShowCard 컴포넌트 코드 로직을 아래와 같이 작성하면 지도 위치 정보를 가져와 readingMap 아톰 상태를 업데이트 할 수 있습니다.

import { useLayoutEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import classes from './Modal.module.scss';
import { readingMap } from '@/states/readingMap';

export default function ShowCard({ cards }) {
  const setMapData = useSetRecoilState(readingMap);

  useLayoutEffect(() => {
    const mapPosition = cards[0].mapData;
    setMapData(mapPosition);
  }, [cards, setMapData]);

  return cards.map((value, index: number) => (
    <div key={index} className={classes.showUsers}>
      {/* <MapReading mapPosition={value.mapData} /> */}
      <span>{value.title}</span>
      <span>{value.address}</span>
      <span>{value.detail}</span>
      <span>{value.cardData.slice(0, 15)}</span>
      <span className={classes.lastSpan}> {value.cardData.slice(16)}</span>
    </div>
  ));
}

readingMap 아톰 상태에 아톰 이펙트를 추가해 업데이트 이력을 기록하도록 구성합니다. (참고)

import { atom } from 'recoil';

export const readingMap = atom({
  key: 'readingMap',
  default: [33.450701, 126.570667],
  effects: [
    ({ onSet }) => {
      onSet((newValue) => {
        console.log('현재 지도 위치 정보:', newValue);
      });
    },
  ],
});

그러면 아래처럼 Console 패널에 업데이트 된 지도 위치 정보가 출력됩니다. (readingMap 아톰 상태 업데이트 로그)

가이드 파일

가이드 파일을 다운로드 받아 확인해보세요.

src.zip