【TypeScript+Next.js】ブログ内にブックマーク機能を追加する

# はじめに
私自身プログラミングでわからないことがあった時は、ドキュメントやいろんな方が書いた記事を読んで問題を解決しています。その際に、解決できた記事についてはブックマークをして保存するようにしてます。ですが、ブックマーク一覧を探すのに手間がかかっていて「探すのめんどくさいな😅」と感じるようになり、**ブログ内にブックマークした一覧を表示できる機能**があればいいと考えました。


サンプルではありますが、私が考えたブックマーク機能がこちらです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/M33TtCaXuSs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 全体のコード
はじめに全体のコードを確認していきましょう。

:::note warn
今回はコンポーネントはなしで記述してます。
:::

```ts:src/pages/index.tsx
import type { NextPage } from 'next';
import Image from 'next/image';
import React, { useState } from 'react';
import Profile from '~/img/profile.png';

interface CardProps {
  title: string;
  text: string;
  image: {
    src: StaticImageData;
    width: number;
    height: number;
    alt: string;
  };
  bookmark: boolean;
}

const Home: NextPage = () => {
  const dataList: CardProps[] = [
    {
      title: 'Title1',
      text: 'テキストテキストテキストテキストテキストテキストテキスト',
      image: {
        src: Profile,
        width: 300,
        height: 200,
        alt: '',
      },
      bookmark: false,
    },
    {
      title: 'Title2',
      text: 'テキストテキストテキストテキストテキストテキストテキスト',
      image: {
        src: Profile,
        width: 300,
        height: 200,
        alt: '',
      },
      bookmark: false,
    },
    {
      title: 'Title3',
      text: 'テキストテキストテキストテキストテキストテキストテキスト',
      image: {
        src: Profile,
        width: 300,
        height: 200,
        alt: '',
      },
      bookmark: false,
    },
  ];
  const [cardList, setCardList] = useState(dataList);

  return (
    <div className='flex'>
      <div className=' grid grid-cols-2 w-2/3'>
        {cardList.map((data, i) => {
          return (
            <div
              key={i}
              className='mt-8 w-[300px]  rounded-lg border border-gray-300 shadow-lg hover:opacity-70 transition duration-500 hover:-translate-y-4'
            >
              <Image
                src={data.image.src}
                width={data.image.width}
                height={data.image.height}
                alt={data.image.alt}
                className='rounded-t-lg'
              />
              <h3 className='mt-2 text-3xl font-semibold tracking-[2px]'>{data.title}</h3>
              <p className='px-3 my-2 text-left'>{data.text}</p>
              <p className='pb-4  mr-4 text-right'>
                {data.bookmark === true ? (
                  <span
                    className='text-3xl text-left text-yellow-400  cursor-pointer select-none'
                    onClick={() => {
                      data.bookmark = false;
                      setCardList([...cardList]);
                    }}
                  >
                    ★
                  </span>
                ) : (
                  <span
                    onClick={() => {
                      data.bookmark = true;
                      setCardList([...cardList]);
                    }}
                    className='text-3xl cursor-pointer select-none'
                  >
                    ☆
                  </span>
                )}
              </p>
            </div>
          );
        })}
      </div>
      <div className='mt-8'>
        <h2 className='text-3xl font-bold'>Bookmark List</h2>
        <div className='overflow-auto h-[500px]'>
          {cardList.map((data, i) => {
            if (data.bookmark === true) {
              return (
                <div
                  key={i}
                  className='mt-4 w-[300px]  rounded-lg border border-gray-300 shadow-lg hover:opacity-70'
                >
                  <Image
                    src={data.image.src}
                    width={data.image.width}
                    height={data.image.height}
                    alt={data.image.alt}
                    className='rounded-t-lg'
                  />
                  <h3 className='mt-2 text-3xl font-semibold tracking-[2px]'>{data.title}</h3>
                  <p className='px-3 my-2 text-left'>{data.text}</p>
                  <p className='pb-4  mr-4 text-right'>
                    {data.bookmark === true ? (
                      <span
                        className='text-3xl text-left text-yellow-400  cursor-pointer select-none'
                        onClick={() => {
                          data.bookmark = false;
                          setCardList([...cardList]);
                        }}
                      >
                        ★
                      </span>
                    ) : (
                      <span
                        onClick={() => {
                          data.bookmark = true;
                          setCardList([...cardList]);
                        }}
                        className='text-3xl cursor-pointer select-none'
                      >
                        ☆
                      </span>
                    )}
                  </p>
                </div>
              );
            }
          })}
        </div>
      </div>
    </div>
  );
};

export default Home;
```


# useStateでブックマークの状態を管理する
ブックマークを管理するのはReack Hooks のuseState を使います。ノートに書いた内容だとこんな感じをイメージしました。
![スクリーンショット 2022-03-18 0.12.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1666696/a9b880b7-9883-3a2b-6973-3dd731f88bf3.png)

ブックマークに登録する箇所のonClickで状態管理をしていきます。クリックされた箇所だけを**falseからtrue** と **trueからfalse** に変更してカードリストの変数を更新するので、onClickでこのように記述していきます。



```tsx: index.tsx
{cardList.map((data, i) => {
            if (data.bookmark === true) {
              return (
                <div
                  key={i}
                  className='mt-4 w-[300px]  rounded-lg border border-gray-300 shadow-lg hover:opacity-70'
                >
                  <Image
                    src={data.image.src}
                    width={data.image.width}
                    height={data.image.height}
                    alt={data.image.alt}
                    className='rounded-t-lg'
                  />
                  <h3 className='mt-2 text-3xl font-semibold tracking-[2px]'>{data.title}</h3>
                  <p className='px-3 my-2 text-left'>{data.text}</p>
                  <p className='pb-4  mr-4 text-right'>
                    {data.bookmark === true ? (
                      <span
                        className='text-3xl text-left text-yellow-400  cursor-pointer select-none'
                        onClick={() => { 　←　// ここで設定
                          data.bookmark = false;
                          setCardList([...cardList]);
                        }}
                      >
                        ★
                      </span>
                    ) : (
                      <span
                        onClick={() => {　　　←　// ここで設定
                          data.bookmark = true;
                          setCardList([...cardList]);
                        }}
                        className='text-3xl cursor-pointer select-none'
                      >
                        ☆
                      </span>
                    )}
                  </p>
                </div>
              );
            }
          })}
```

最後に、ブックマークのエリア内にtrueの時はカードリストの変数が表示されるように記述をしてきます。
```tsx: index.tsx
<div className='overflow-auto h-[500px]'>
          {cardList.map((data, i) => {
            if (data.bookmark === true) {
              return (
                <div
                  key={i}
                  className='mt-4 w-[300px]  rounded-lg border border-gray-300 shadow-lg hover:opacity-70'
                >
                  <Image
                    src={data.image.src}
                    width={data.image.width}
                    height={data.image.height}
                    alt={data.image.alt}
                    className='rounded-t-lg'
                  />
                  <h3 className='mt-2 text-3xl font-semibold tracking-[2px]'>{data.title}</h3>
                  <p className='px-3 my-2 text-left'>{data.text}</p>
                  <p className='pb-4  mr-4 text-right'>
                    {data.bookmark === true ? (
                      <span
                        className='text-3xl text-left text-yellow-400  cursor-pointer select-none'
                        onClick={() => {
                          data.bookmark = false;
                          setCardList([...cardList]);
                        }}
                      >
                        ★
                      </span>
                    ) : (
                      <span
                        onClick={() => {
                          data.bookmark = true;
                          setCardList([...cardList]);
                        }}
                        className='text-3xl cursor-pointer select-none'
                      >
                        ☆
                      </span>
                    )}
                  </p>
                </div>
              );
            }
          })}
        </div>
```


# 最後に
リロードした時などはブックマークの記録は消えてしまうので、データベースやローカルストレージなどに保存をしておかなければブックマークの情報は初期状態に戻るのでその辺りはご自身の好きな方法で保存を検討してみてください！

最後までお読みいただきありがとうございます。