【React】useRefの応用、ひとつ前の状態と現在の状態を比較する方法(usePrevious)
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を含めます。