본문 바로가기
CodeIt_Sprint/React로 데이터 다루기

(8)React로 데이터 다루기_8_ "더보기" 버튼 비활성화 방법2, 비동기함수 구현시 주의해야할점 => 콜백함수로 예약시점의 값 변화 X , 현재시점의 값 변화 O

by 코잼민 2025. 1. 2.

● 이론0_ hasNext를 이용하여, '더보기' 버튼을 조건부로 보였다, 안보였다 기능으로 코드 작성해보기

앞에서 논리 연산자 && 을 사용해서 간단한 조건부 렌더링을 해봤는데요.

이번 레슨에선 리액트에서 활용할 수 있는 조건부 렌더링에 대한 꿀팁을 알려드릴게요!

논리 연산자 활용하기

AND 연산자

 
import { useState } from 'react';

function App() {
  const [show, setShow] = useState(false);

  const handleClick = () => setShow(!show);

  return (
    <div>
      <button onClick={handleClick}>토글</button>
      {show && <p>보인다 👀</p>}
    </div>
  );
}

export default App;

show 값이 true 이면 렌더링 하고, false 이면 렌더링 하지 않습니다.

OR 연산자

 
import { useState } from 'react';

function App() {
  const [hide, setHide] = useState(true);

  const handleClick = () => setHide(!hide);

  return (
    <div>
      <button onClick={handleClick}>토글</button>
      {hide || <p>보인다 👀</p>}
    </div>
  );
}

export default App;

hide 값이 true 이면 렌더링 하지 않고, false 이면 렌더링 합니다.

삼항 연산자 활용하기

 
import { useState } from 'react';

function App() {
  const [toggle, setToggle] = useState(false);

  const handleClick = () => setToggle(!toggle);

  return (
    <div>
      <button onClick={handleClick}>토글</button>
      {toggle ? <p>✅</p> : <p>❎</p>}
    </div>
  );
}

export default App;

삼항 연산자를 사용하면 참, 거짓일 경우에 다르게 렌더링해줄 수 있습니다.

toggle 의 값이 참일 경우엔 '✅'을, 거짓일 경우에는 '❎'를 렌더링합니다.

렌더링되지 않는 값들

 
function App() {
  const nullValue = null;
  const undefinedValue = undefined;
  const trueValue = true;
  const falseValue = false;
  const emptyString = '';
  const emptyArray = [];

  return (
    <div>
      <p>{nullValue}</p>
      <p>{undefinedValue}</p>
      <p>{trueValue}</p>
      <p>{falseValue}</p>
      <p>{emptyString}</p>
      <p>{emptyArray}</p>
    </div>
  );
}

export default App;

위 컴포넌트에서 중괄호 안에 있는 값들은 모두 아무것도 렌더링하지 않습니다.

 
function App() {
  const zero = 0;
  const one = 1;

  return (
    <div>
      <p>{zero}</p>
      <p>{one}</p>
    </div>
  );
}

export default App;

반면에 이 값들은 각각 숫자 0과 1을 렌더링 합니다.

조건부 렌더링을 사용할 때 주의할 점

만약 아래와 같은 코드를 사용하면 어떤 문제가 있을까요?

 
import { useState } from 'react';

function App() {
  const [num, setNum] = useState(0);

  const handleClick = () => setNum(num + 1);

  return (
    <div>
      <button onClick={handleClick}>더하기</button>
      {num && <p>num이 0 보다 크다!</p>}
    </div>
  );
}

export default App;

num 값이 0일 때는 false 로 계산되니까 뒤의 값을 계산하지 않기 때문에

아무것도 렌더링 하지 않는 코드 같습니다.

하지만 앞에서 살펴봤듯이 숫자 0은 0으로 렌더링 되는데요.

그래서 처음 실행했을 때 숫자 0이 렌더링 되고

'더하기' 버튼을 눌러서 num 값이 증가하면 num이 0 보다 크다! 가 렌더링 됩니다.

그래서 이런 경우엔 아래처럼 보다 명확한 논리식을 써주는 게 안전합니다.

true 나 false 값은 리액트에서 렌더링 하지 않기 때문이죠!

 
import { useState } from 'react';

function App() {
  const [num, setNum] = useState(0);

  const handleClick = () => setNum(num + 1);

  return (
    <div>
      <button onClick={handleClick}>더하기</button>
      {(num > 0) && <p>num이 0 보다 크다!</p>}
    </div>
  );
}

export default App;

편하게 코드를 작성하다 보면 굉장히 자주 하는 실수니까 함께 알아두시면 좋을 겁니다!

 

● 이론1_ offline 상태에서 || 3G 상태에서 , '더보기' 버튼을 누른 뒤, '삭제' 버튼을 누르면 어떤 결과가 나올까?

○ 결과 : 삭제했던 요소가 다시 생겨서 비동기 작업

○ 이유 : 비동기함수 작동 시점의 items를 저장한뒤, 삭제작업 후의 items에서 비동기를 실행하는 것이 아닌, 작동 시점의 items에서 "더보기"를 수행하기 때문이다.

 

○ 해결방법 : 비동기 실행 부분을 콜백함수의 매개변수로 작동이 수행된 시점의 items를 전달한다.

  const loadReviews = async (options) => {
    const { reviews, paging } = await getReviews(options);

    if (options.offset === 0) {
      setItems(reviews);
    } else {
      setItems((prevItems) => [...prevItems, ...reviews]);
    }

    setOffset(options.offset + items.length);
    setHasNext(paging.hasNext);
  };

 

◎ 연습문제 : 커서 페이지네이션 구현해보기

실습 설명

이번엔 페이지네이션 API를 사용해 보겠습니다. 영상에서 다룬 내용이랑 다르게, 커서 기반의 페이지네이션을 사용할게요.

아래 리퀘스트 예시를 참고해서 App.js 파일을 수정해 주세요.

처음 리퀘스트

GET https://learn.codeit.kr/api/foods?order=createdAt&limit=10

리스폰스

{
  "foods": [...],
  "paging": {
    "count": 42,
    "nextCursor": "Y3JlYXRlZEF0LDIz"
  }
}

(nextCursor 의 값은 실습 서버의 상황에따라 달라질 수 있음)

다음 리퀘스트

앞의 리스폰스에서 도착한 nextCursor 값을 활용해서 cursor 쿼리로 리퀘스트를 보냅니다.

GET https://learn.codeit.kr/api/foods?cursor=Y3JlYXRlZEF0LDIz&limit=10

만약 리스폰스의 paging.nextCursor 가 null 이라면 '더보기' 버튼을 못 누르게 합니다.

● 풀이과정 :

○ 순서1_ cursor 변수로 useState 등록

○ 순서2_ handleLoad 수정 :

  • getFood() 메소드 부분 : foods , paging : {nextCursor}, //nextCursor 추출용
  • option.cursor값===false // 맨 밑까지 커서가 내려가지 않았다는 뜻 => 그대로 추출 : setItems(foods);
  • option.cursor값===true // 현재 커서가 맨밑으로 내려감 => setItems((prevItems) => [...prevItems, ...foods]); /추가 배열 적용
  • cursor 업데이트 : setCursor(nextCursor);

○ 순서3_ handleMoreLoad 핸들러 구현 :

○ 순서4_ useEffect 코드 수정

 

○ 순서5_ 버튼 태그 추가

 

정답 : 

import { useEffect, useState } from 'react';
import { getFoods } from '../api';
import FoodList from './FoodList';

function App() {
  const [order, setOrder] = useState('createdAt');
  const [cursor, setCursor] = useState(null);
  const [items, setItems] = useState([]);

  const handleNewestClick = () => setOrder('createdAt');

  const handleCalorieClick = () => setOrder('calorie');

  const handleDelete = (id) => {
    const nextItems = items.filter((item) => item.id !== id);
    setItems(nextItems);
  };

  const handleLoad = async (options) => {
    const {
      foods,
      paging: { nextCursor },
    } = await getFoods(options);
    if (!options.cursor) {
      setItems(foods);
    } else {
      setItems((prevItems) => [...prevItems, ...foods]);
    }
    setCursor(nextCursor);
  };

  const handleLoadMore = () => {
    handleLoad({
      order,
      cursor,
    });
  };

  const sortedItems = items.sort((a, b) => b[order] - a[order]);

  useEffect(() => {
    handleLoad({
      order,
    });
  }, [order]);

  return (
    <div>
      <button onClick={handleNewestClick}>최신순</button>
      <button onClick={handleCalorieClick}>칼로리순</button>
      <FoodList items={sortedItems} onDelete={handleDelete} />
      {cursor && <button onClick={handleLoadMore}>더보기</button>}
    </div>
  );
}

export default App;