やりたいこと
お店の名前、評価のデータ一覧が入った配列が親コンポーネント<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
をソートしようとすると、オブジェクトの変更が検知されず、再レンダリングが走らない。