Local Storageを使ってCardコンポーネントの並べ替え結果を保存する


## はじめに
さて，今回は前回の続きです．背景やレイアウトの実装は[こちら](https://qiita.com/Hayato-7812/items/fd2431fac570a367ad9b)の記事に記載したので，まだ読んでいない方はこちらの記事からご覧ください．

今回は**Cardコンポーネントの並べ替え結果をLocal Storageに保存する**ことを目標に実装を進めていきます．この機能を実装することによって

- ユーザー(クライアント)が個々で並べ替えたコンポーネントによるレイアウトを次回サイト訪問時にも変わらず確認することができる

という点で便利です．(インターン先で開発しているアプリケーションには必須の機能でした)

今回はこの機能の実装方法を調査したことと合わせて，メモとして記載したいと考えています．

とはいえ，前回の時点でレイアウトの部分は既に実装してあるので，今回の記事はそこまで大きなボリュームにはなりません．

## Local Storageとは

[前回の記事にも記載](https://qiita.com/Hayato-7812/items/fd2431fac570a367ad9b)しましたが，大事なことなので２回書きます．

Local Storageとは，**クライアントのデータを各々のブラウザ上に保存する**ことができる(JavaScriptを使った)仕組みです． 5〜10MBまでのデータを永続的に保存できます．

今回の機能では，CardコンポーネントのindexをこのLocal Storageに保存することで目標を実現します．(※一部のセキュリティ攻撃に対して脆弱であるため，ユーザー情報等を保存することは避けましょう．)

参考：

- [WebStorageのセキュリティに関する重要な考慮点と対策 - 開発者が知っておくべきこと - deve.K's Programming Primer - プログラミング初心者のための入門ブログ](https://dev-k.hatenablog.com/entry/js-localStorage-and-localStorage-dev-k)
- [jQuery UI Sortable は並び順を保存しなければ意味がない | hijiriworld Web](https://hijiriworld.com/web/jquery-ui-sortable/)

## Local Storageの情報を保存，取得する

以下の方法でLocal Storageに任意のkeyとvalueのセットを保存，取得することができます．
たった1行でできるなんてやはりJavaScriptは初学者にも優しいですね．

1. 保存
    
    ```jsx
    localStorage.setItem("myCat", "Tom");
    ```
    
2. 取得
    
    ```jsx
    const cat = localStorage.getItem("myCat");
    ```
    

今回の記事では以上の2つを使いますが，詳細について知りたい方は，以下のMDNのドキュメントをご覧ください．

[Window: localStorage プロパティ - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage)

補足：

Chromeの開発者ツールでは，「Application」から現在のLocal Storageの状況を確認できます．

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071957/4908ec95-f2f6-1afa-b659-07a72859c67e.png)


## 実装

実装する機能をもう少し細かく切り分けると，

1. ページにアクセス
2. Local Storageの情報を取得
3. Cardコンポーネントの表示順序位(初期値)を確定
    a. Local Storageに前回設定した表示順序(`indexArray`)が保存されていたら採用
    b. もしaの情報がない(前回アクセス時に設定していない)場合．デフォルトの表示順序[1,2,3,…]を指定．
4. (親)indexページ及び，(子)Cardコンポーネントの表示 ([前回](https://qiita.com/Hayato-7812/items/fd2431fac570a367ad9b))
5. 並べ替えがあれば新たに並び替えた結果をLocal Storage(`indexArray`)に保存

とうようになります．

## 2. Local Storageの情報を取得 & 3. Cardコンポーネントの表示順序位(初期値)を確定
2はuseEffectを使い，表示データに変更があったときにこの処理を実行するようにします．

3は2の結果を基に条件分岐を行い，表示順序を確定します．

※Local Storageから取得した値は文字列なので、それを JavaScript のオブジェクトに変換するために JSON.parse を使用しています．

```tsx
const {cardData} = getCardData(); //cardに表示するデータは仕様に合わせて実装してください．
const [items, setItems] = React.useState([]);
  
React.useEffect(() => {
    // Local Storageに表示順序データが保存されているか
    const indexArray = JSON.parse(localStorage.getItem('indexArray')) ?? Array.from({ length: card_data.length }, (_, index) => index + 1);
    setItems(indexArray);
}, [cardData]);
```
※@onjhthrw368様よりご指摘をいただき，コードを一部修正しました．

## 5. 並べ替えがあれば新たに並び替えた結果をLocal Storage(`indexArray`)に保存

5の「並べ替えがある」とは即ち，「Cardコンポーネント同士がドラッグ&ドロップ」されるということなので，前回実装した`handleDragEnd`(ドラッグ&ドロップが終わったときに実行される関数)に対して以下の変更を加えます．

```tsx
function handleDragEnd(event) {
    const {active, over} = event;
    if (active.id !== over.id) {
      const oldIndex = items.indexOf(active.id);
      const newIndex = items.indexOf(over.id);
      setItems((items) => {
        return arrayMove(items, oldIndex, newIndex);
      });
      localStorage.setItem('indexArray', JSON.stringify(arrayMove(items, oldIndex, newIndex))); // 5
    }
  }
```
<br>

以上で実装は完了です．
改めて全体を記載すると，以下のようになります．

<details><summary>コード(最終結果)</summary>

```tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Card } from '../../Card';
import { Grid, Box } from '@mui/material';
import { SortableContext, arrayMove, rectSortingStrategy } from "@dnd-kit/sortable";
import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core';

export const Index = () => { 
  const {card_data} = getCardData(); //cardに表示するデータは仕様に合わせて実装してください．
 
  //  for sorting Card
  const [items, setItems] = React.useState([]);
  const sensors = useSensors(useSensor(PointerSensor));
	
	React.useEffect(() => {
    console.log("useEffect");
    if (localStorage.getItem('indexArray')) {
      var indexArray = JSON.parse(localStorage.getItem('indexArray'));
    }else {
      var indexArray = [1,2,3,4]
    }
    setItems(indexArray);
  }, [cardData]);

  function handleDragEnd(event) {
    const {active, over} = event;
    if (active.id !== over.id) {
			const oldIndex = items.indexOf(active.id);
      const newIndex = items.indexOf(over.id);
      setItems((items) => {
        return arrayMove(items, oldIndex, newIndex);
      });
			localStorage.setItem('indexArray', JSON.stringify(arrayMove(items, oldIndex, newIndex)));
    }
  }
   
	// 引数のidを自身のidプロパティに所有するCardComponentを取得する
  const getCard = (id) => {
    const data = card_data.find((item) => item.id === id);
    return data ? (
      <Grid item xs={4} sx={{ padding: 0 }} key={data.name}>
        <Card
					id={data.id}
          name={data.name}
        />
      </Grid>
    ) : null;
  }

  return (
    <>
    <DndContext 
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragEnd={handleDragEnd}
    >
      <SortableContext 
        items={items}
        strategy={rectSortingStrategy}
      >
      <Box sx={{ flexGrow: 1 }}>
        <Grid container spacing={2}>
          items.map(id => getCard(id))} 
        </Grid>
      </Box>   
      </SortableContext>
    </DndContext>
  </>
  );
};

ReactDOM.render(<Index />, document.getElementById('Index'));
```

</details>

## 結果

実際に表示してみると，思惑通りに

- 最初は[1,2,3,4]とデフォルトの順番でCardが表示される
- 並べ替えをするとLocalStorageにその結果が保存される
- リロードすると前回に設定した表示順序が再度表示される

ことが確認できました．

![%E7%94%BB%E9%9D%A2%E5%8F%8E%E9%8C%B2-2023-09-24-14.41.37.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071957/bc105cb4-c77e-6bea-fe18-fe05af28ae96.gif)

## まとめ

今回は，Local Storageを使ってCardコンポーネントの並べ替え結果を保存する方法について，自身で調べたことや実装結果をまとめました．

前回の実装で苦労した甲斐あってか，今回は僅かな変更で目標を実現できたので，とてもスムーズでした．(毎回このくらいパッパとできたらいいのに…)

ただ，LocalStorageとはなんぞやというところから，Cookieとの違い，セキュリティ上の注意点など，事前学習は自分にとってとても有意義な時間になったので，今回のような実装を経験できてよかったです．

最後まで読んでいただき，ありがとうございました．

## 参考文献

[【簡易メモ保存作成】ローカルストレージを使ってみましょう｜超初心者のためのReact入門](https://zenn.dev/hamaup/books/4b93f6b56e3605/viewer/3dc124)

[副作用フックの利用法 – React](https://ja.legacy.reactjs.org/docs/hooks-effect.html)