【TypeScript+Next.js】 React Hooksで簡易的な検索機能を実装する

#  はじめに
今回はReact HooksのuseStateとuseEffectを使って簡易的な検索機能を紹介します。
正直ライブラリを使ったら楽なんですが、useStateやuseEffectの使い方に慣れるための自己学習用としてご覧いただけたら幸いです。

環境
Next.js
React
Tailwindcss
TypeScirpt


完成した時のデモ画像がこちらです。

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



# 全体コードの確認

```ts:Card.tsx
import Image from 'next/image';
import React from 'react';

interface ImageProps {
  src: StaticImageData;
  width: number;
  height: number;
  alt: string;
}

export interface CardProps {
  image: ImageProps;
  title: string;
  text: string;
  button: {
    text: string;
    url: string;
  };
}

export const Card: React.FC<CardProps> = ({ image, title, text, button }) => {
  return (
    <div className='w-[300px] text-center rounded-lg shadow-lg '>
      <Image
        src={image.src}
        height={image.height}
        width={image.width}
        alt={image.alt}
        className=' rounded-t-lg'
      />
      <h2 className='my-2 text-xl font-bold'>{title}</h2>
      <p className='px-4 text-sm text-left'>{text}</p>
      <button className='py-1.5 px-4 my-4 text-sm  bg-yellow-500 rounded-full shadow-md'>
        {button.text}
      </button>
    </div>
  );
};

```

```ts:Sreach.tsx
import { useState, useEffect } from 'react';
import { Card, CardProps } from '@/component/Card';
import ProImage from '~/img/profile.png';
const data: CardProps[] = [
  {
    image: {
      src: ProImage,
      width: 300,
      height: 210,
      alt: 'profile',
    },
    title: 'JavaScript',
    text: 'テキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト',
    button: {
      text: 'Read More',
      url: 'https://google.com',
    },
  },
  {
    image: {
      src: ProImage,
      width: 300,
      height: 210,
      alt: 'profile',
    },
    title: 'React',
    text: 'テキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト',
    button: {
      text: 'Read More',
      url: 'https://google.com',
    },
  },
  {
    image: {
      src: ProImage,
      width: 300,
      height: 210,
      alt: 'profile',
    },
    title: 'TypeScript',
    text: 'テキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト',
    button: {
      text: 'Read More',
      url: 'https://google.com',
    },
  },
  {
    image: {
      src: ProImage,
      width: 300,
      height: 210,
      alt: 'profile',
    },
    title: 'Next.js',
    text: 'テキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト',
    button: {
      text: 'Read More',
      url: 'https://google.com',
    },
  },
];
export const Search: React.FC = () => {
  const [showItems, setShowItems] = useState<CardProps[]>([]);

  useEffect(() => {
    setShowItems(data);
  }, []);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const result = data.filter((item) => {
      return item.title.toLowerCase().match(e.target.value.toLowerCase());
    });
    console.log(result);
    setShowItems(result);
  };

  return (
    <div>
      <form action='' className='flex justify-center'>
        <input
          type='text'
          className='my-8  rounded border border-black'
          placeholder='search'
          onChange={(e) => handleChange(e)}
        />
      </form>
      <div className='flex justify-between'>
        {showItems.map((item, i) => {
          return (
            <>
              <Card image={item.image} title={item.title} text={item.text} button={item.button} />
            </>
          );
        })}
      </div>
    </div>
  );
};

```

```ts.index.tsx
import type { NextPage } from 'next';
import { Search } from '@/component/Search';

const Home: NextPage = () => {
  return (
    <div className='font-bold'>
      <Search />
    </div>
  );
};

export default Home;

```


:::note warn
Cart.tsxとindex.tsxのコードは省略させていただきます🙇‍♂️
:::


# 検索をするためには？
検索をする時はまず文字を入力する必要があります。今回は**入力した値が含まれているもの**を表示させるようにすればいいだけです。inputタグのtypeをtextと指定します。

```ts
<form action='' className='flex justify-center'>
  <input
  type='text'
  className='my-8  rounded border border-black'
  placeholder='search'
  onChange={(e) => handleChange(e)}
  />
</form>
```

あとは入力された際のビジネスロジックを書くのとデータを用意すればOKです。Reactで用意されているフォームイベントのonChangeの中にロジックを記入します。

```ts
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const [showItems, setShowItems] = useState<CardProps[]>([]);
    
    useEffect(() => {
    setShowItems(data);
    }, []);
    ...

    const result = data.filter((item) => {
      return item.title.toLowerCase().match(e.target.value.toLowerCase());
    });
    setShowItems(result);
  };
```

フォームの中に入力されるたびにstate変数のshowItemsを更新していくようにします。今回はカードコンポーネントのタイトルを基準にしていますが、テキストを基準にすることもできます。その時は` item.title.toLowerCase().match(e.target.value.toLowerCase());`の中のtitleを**text**に変更してください。
補足にはなりますが、`.toLowerCase()`にしているのは**大文字から小文字**に直している処理です

入力した検索結果をstate変数に代入をしていき、入力したときと入力した文字を削除するときに更新されていきます。　
あとはカードコンポーネントの中にデータを入れていけば完成です！

useEffectを使っているのは、ページがレンダリングされる前にstate変数にデータを代入させるために使ってます。

:::note info
**余談**
私自身ビジネスロジックのコードを書いてる途中で手が止まることがあります。その時は入力するデータと出力されるデータの**入口と出口**を決めてコードを書くように心がけてます。例えば、**stringからnumberの値が欲しい**だったり、**インデックス番号2の配列からstringが欲しい**みたいに考えたら案外スッキリとコードが書けたりできるのでぜひ試してみてください！
:::


# 最後に
実装してみると案外呆気ないものでした😄笑
React　Hooksの練習にもなるので実際に手を動かして試してみてください！
最後まで読んでいただきありがとうございました！