【React】useRefの応用、ひとつ前の状態と現在の状態を比較する方法(usePrevious)

React

Reactで状態管理をしていると、「ひとつ前の状態」と「現在の状態」を比較する条件分岐したい場合などが多々あります。
そんなときに使えるのが、useRefを使ったusePreviousという方法です。

useRefがあいまいな場合は、【React】でDOMを直接操作するuseRefの使い方とrefとstateの違いを参考にしてみてください。

ひとつ前の状態と現在の状態を比較する方法(usePrevious)

まず、usePreviousというカスタムフックを作成します。

import { useEffect, useRef } from 'react';

// カスタムフック usePrevious を作成
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

export default usePrevious;

usePreviousはカスタムフックで、useRefを使っています。
ref.currectの値を格納して戻り値として返しています。

usePreviousを使用してひとつ前の状態と現在の状態を比較する

import React, { useEffect, useState } from 'react';
import useCatalogData from './helpers/useCatalogData';
import usePrevious from './helpers/usePrevious';


// サイドバーのコンポーネント
const Sidebar = ({ cartItems, onRemoveFromCart, show, toggleMenu }) => {
  const catalogData = useCatalogData();

  // 最新のアイテムを取得する関数
  const getLatestItem = latestItemId => {
    let latestItem = null;
    for (const category of catalogData) {
      for (const item of category.items) {
        if (item.id === latestItemId) {
          latestItem = item;
          break;
        }
      }
      if (latestItem) {
        break;
      }
    }
    return latestItem;
  };

  // cartItems の最新のアイテムIDを取得
  const latestItemId =
    cartItems.length > 0 ? cartItems[cartItems.length - 1] : null;
  // 最新のアイテムを取得
  const [latestItem, setLatestItem] = useState(
    latestItemId ? getLatestItem(latestItemId) : null
  );

  // cartItems の変更を監視し、変更があった場合に latestItem を更新
  const prevCartItemsLength = usePrevious(cartItems.length); // 前回の cartItems の長さを記録

  const [isCartItemsIncreased, setisCartItemsIncreased] = useState(false);

  // cartItems の変更を監視し、変更があった場合に latestItem を更新
  useEffect(() => {
    if (cartItems.length === 0) {
      setLatestItem(null); // カートが空の場合、最新アイテムを null に設定
    } else {
      const latestItemId = cartItems[cartItems.length - 1];
      const updatedLatestItem = getLatestItem(latestItemId);
      setLatestItem(updatedLatestItem);
    }

    // 現在の cartItems の長さと前回の cartItems の長さを比較
    if (cartItems.length > prevCartItemsLength) {
      // cartItems が増加した場合の処理
      // console.log('Cart items increased');
      setisCartItemsIncreased(true);
    // } else if (cartItems.length < prevCartItemsLength) {
    } else  {
      // cartItems が減少した or 同じ場合の処理
      // console.log('Cart items decreased or are same');
      setisCartItemsIncreased(false);
    }

    // showでチェックリストがオープンになったときに増えているか確認
  }, [cartItems, show]);


  return (
    <aside className={`sidebar ${show ? 'show' : ''}`} id="sidebar">
      <div className="sidebar__close-area u-pc-none" onClick={toggleMenu}>
          {cartItems.length !== 0 && (
            <a className="sidebar__button" href="/contact/catalog/">
              <span className="-text">ダウンロード</span>
              <div
                className="icon-catalog-download"
                dangerouslySetInnerHTML={{ __html: IconCatalogDownload }}
              />
            </a>
          )}
        </nav>
      </div>
    </aside>
  );
};

export default Sidebar;

usePreviousの値をprevCartItemsLengthという変数に格納しています。
さらに、カート内が増えたかどうかを判定するための変数isCartItemsIncreasedをuseStateを使って用意しています。

あとは、if (cartItems.length > prevCartItemsLength)のようにして比較することで条件分岐することができます。

大事なポイントとして、useEffect内で使用します。
そして、上のケースでは、cartItemsが更新されるたびに再レンダリングされるように、useEffectの第二引数にcartItemsを含めます。

React

Posted by devsakaso