やりたいこと
お店の名前、評価のデータ一覧が入った配列が親コンポーネント<App>から渡され、 <ShopList> コンポーネントでmapを使ってさらに子供のコンポーネント<ShopItem>にデータ展開している。
この<ShopList> コンポーネントの中に、「並び替えボタン」を設置し、クリック時に評価順にソートして表示する機能を追加したい。
export type Shop = {
name: string;
rating: number;
};
const shops:Shop[] = [
{
name: 'shopA',
rating: 1.2,
},
{
name: 'shopB',
rating: 2.6,
},
{
name: 'shopC',
rating: 0.9,
},
{
name: 'shopD',
rating: 3.1,
},
];
const App = () => {
return <ShopList shops={shops} />;
};
export default App;
import { Shop } from './App'
import ShopItem from './ShopItem';
type Props = {
shops: Shop[];
};
const ShopList = ({ shops }: Props) => {
return (
<>
{shops.map((item, i) => (
<ShopItem shop={item} key={i} />
))}
</>
);
};
export default ShopList;
実装方法
ShopList.tsx を以下のように修正する。
import { useState, useEffect } from 'react';
import { Shop } from './App'
import ShopItem from './ShopItem';
type Props = {
shops: Shop[];
};
const ShopList = ({ shops }: Props) => {
const [sortedShops, setSortedShops] = useState<Shop[]>(shops);
useEffect(() => {
setSortedShops(shops);
}, [shops]);
const sortByRating = () => {
var clonedShops = Array.from(sortedShops);
clonedShops.sort((a, b) => b.rating - a.rating);
setSortedShops(clonedShops);
};
return (
<>
<button onClick={sortByRating}>並び替えボタン</button>
{sortedShops.map((item, i) => (
<ShopItem shop={item} key={i} />
))}
</>
);
};
export default ShopList;
実装のポイント
- useState()の初期値をuseEffect内で設定すること
useEffect内で setSortedShops を呼ぶことで、propsで受け取った値を sortedShops として設定することができる。React.useState()の初期値は一度しかセットされないため、useState()の初期値にpropsを使う場合はuseEffect()の中で値を設定することで、propsが変化した時に親から受け取ったshopsをsetすることでができる。
参考:【React】useState()の初期値にpropsを使う時の注意点
- sortメソッドの結果を一時変数に入れる
sortメソッドを使ってお店の評価( rating )によるソートを行ったものを一時変数( clonedShops)に入れて、 setSortedShops することで、React側がオブジェクトの変更を検知し再レンダリングが走る。一時変数を使わず、直接 sortedShops をソートしようとすると、オブジェクトの変更が検知されず、再レンダリングが走らない。