1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

タグぽちで即フィルター!Remix+microCMSで作るゆるっとタグ検索機能

Posted at

こんにちは、コードいじり大好き人間です 👋
今回はずっと「あとでやろ〜」って放置してたタグ検索機能を、ついに!実装しました。
勢いで作ったので、その備忘録をここにドーンと置いておきます💪

📄 概要

Remix と microCMS を使ったブログに、タグをポチッと押すだけで記事一覧を絞り込める機能を入れました。
useSearchParams で URL にタグ名くっつけて、loader 側でフィルタかける感じです。
記事詳細ページからもタグ付きで一覧に戻れるようになってます。ちょっと便利でしょ?

💡 今回の実装で考えたこと

Remix には action っていう、フォーム送信とか画面の操作に合わせて非同期処理を動かすイケてる機能があります。
結果は useActionData で受け取れるんだけど、今回はあえて使いませんでした。

理由はカンタン、
useActionData と loader のどっち使うの?」「うわ、ややこしい…」って未来が見えたから🤣

なので今回は、タグクリック → URL にクエリ追加 → loader 再実行! っていうシンプル路線にしました。

🔍 microCMS の仕様チェック

docs をポチポチ見てたら、filters パラメータにタグ突っ込むと検索できることが判明。
タグはカンマ区切りにしてあって、contains を使えば部分一致検索もバッチリ。

client
  .get({
    endpoint: 'posts',
    queries: { filters: 'tags[contains]タグ名' }
  })
  .then((res) => console.log(res));

これ動いたとき「おぉ〜動いた!」ってひとりでニヤけたのは秘密です🤫

📝 投稿一覧側の対応

タグを押したら URL にタグ名をセット、useSearchParams でパラメータ更新。
すると自動で loader が再実行されて、新しい一覧が出てくる…うん、これ好き。

実装例(ContentCard)

import { useSearchParams } from "@remix-run/react";

export function ContentCard({ title, date, tags }: Props) {
  const [, setSearchParams] = useSearchParams();
  return (
    <article className="card w-full">
      <div className="card-body flex flex-col gap-4">
        {tags && (
          <div className="flex gap-2">
            {tags.map((tag, index) => (
              <button
                type="button"
                onClick={() => {
                  const params = new URLSearchParams();
                  params.set("tag", encodeURI(tag));
                  setSearchParams(params);
                }}
                key={index}
                className="btn btn-accent btn-xs rounded-full text-xs"
              >
                {tag}
              </button>
            ))}
          </div>
        )}
      </div>
    </article>
  );
}

⚙️ loader 側の修正

export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
  const parseRequest = zx.parseQuerySafe(request, {
    tag: z.string().optional(),
  });

  if (!parseRequest.success) return;

  try {
    const queryTag = parseRequest.data.tag;
    const result = await client.get<PostsResult>({
      endpoint: "posts",
      queries: {
        limit: 10,
        offset: 0,
        filters:
          queryTag === undefined
            ? undefined
            : `tags[contains]${decodeURI(queryTag)}`,
      },
    });

    return result;
  } catch (error) {
    console.error("posts clientLoader error", error);
    return;
  }
};

microCMS はエンコードされたままだと検索してくれないので、decodeURI は忘れずに。
(忘れて30分くらい首をひねったのはワタシです…😇)

📄 記事詳細ページ側の対応

こっちはめちゃ簡単。useNavigate でタグ付きの一覧ページに飛ばすだけ。
$path は Remix Routes の型付きルーティングです。

export default function PostId() {
  const contents = useLoaderData<typeof clientLoader>();
  const navigate = useNavigate();
  return (
    <div className="card bg-base-100 w-[80%] h-full">
      <div className="card-body flex flex-col gap-4">
        <div className="flex gap-2">
          {contents.tags?.split(",").map((tag, index) => (
            <button
              type="button"
              onClick={() => {
                navigate(
                  $path("/posts", {
                    tag: encodeURI(tag),
                  }),
                );
              }}
              key={index}
              className="btn btn-accent btn-xs rounded-full text-xs "
            >
              {tag}
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

📌 まとめ

  • タグクリックでサクッと絞り込み
  • 詳細ページからもタグ付きで一覧に戻れる
  • microCMS の contains で部分一致OK
  • decodeURI 忘れると地獄を見る(体験談)

ここまで目を通してくださってありがとうございました!!!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?