やかんです。

「新機能!」ってほどでもないんですが、ささやかながらブログに新しい機能をつけました。

東大生やかんのブログ
やかん

ほんっとささやか。

今まで記事一覧はリスト表示のみでした。これ↓

で、今回追加したのがこちら↓

動画でお示しするとこんな感じ↓

使いやすくなっていたら嬉しいな!といったところです。

「とりあえず機能だけ実装した」感じなのでコードはめちゃ汚いですが、「nextjsのserver componentを使いつつ、どうやって投稿データを蓄積していくの?」という場合の参考になればということでコード貼ります。

import { PostGrid } from "@/components/post-grid";
import { PostList } from "@/components/post-list";
import { getPosts } from "@/lib/get-posts";
import type { Post } from "@/lib/type";

type Props = {
  page: number;
  layout: "list" | "grid";
};

export async function Posts({ page, layout }: Props) {
  const posts: Post[] = await getPosts({ page });

  return layout === "list" ? (
    <PostList posts={posts} page={page} listPagePath={() => `/posts`} />
  ) : (
    <PostGrid posts={posts} page={page} />
  );
}
"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { BsChevronDoubleDown } from "react-icons/bs";

import { PostGridCard } from "@/components/post-grid-card";
import { buttonVariants } from "@/components/ui/button";
import { BeatLoader } from "@/components/ui/loader";
import { WidthWrapper } from "@/components/width-wrapper";
import type { Post } from "@/lib/type";
import { cn } from "@/lib/utils";

type Props = {
  posts: Post[];
  page: number;
};

export function PostGrid({ posts: initialPosts, page }: Props) {
  const [posts, setPosts] = useState<Post[]>();
  const [loading, setLoading] = useState<boolean>(false);

  const router = useRouter();

  useEffect(() => {
    const newPosts = posts ? [...posts, ...initialPosts] : initialPosts;
    // idが同じ投稿を削除
    const uniquePosts = newPosts.filter(
      (post, index, self) => index === self.findIndex((p) => p.id === post.id),
    );

    // 1ページ目じゃないのに10件しかないということは、それ以前のページのデータが取得できていないということ
    if (page !== 1 && uniquePosts.length === 10) {
      router.push("/posts?layout=grid&page=1");
      return;
    }

    setPosts(uniquePosts);
    setLoading(false);

    // 一番下にスクロール
    if (page !== 1) {
      window.scrollTo(0, document.body.scrollHeight);
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [initialPosts]);

  return posts ? (
    <WidthWrapper>
      <div className="grid grid-cols-3 gap-8">
        {posts?.map((post) => <PostGridCard key={post.id} post={post} />)}
      </div>
      <Link
        href={`?layout=grid&page=${page + 1}`}
        onClick={() => setLoading(true)}
        className={cn(
          buttonVariants({ variant: "outline" }),
          "my-8 flex w-full items-center justify-center space-x-2 font-bold",
        )}
      >
        {loading ? (
          <BeatLoader color="#475569" />
        ) : (
          <>
            <span>さらに表示</span>
            <BsChevronDoubleDown className="font-bold text-black" />
          </>
        )}
      </Link>
    </WidthWrapper>
  ) : (
    <div className="mt-[20vh] flex min-h-screen justify-center">
      <BeatLoader color="#475569" />
    </div>
  );
}

うーん、もっといいやり方絶対あるよな。ハリボテすぎる。まあ、追ってリファクタしていきます!

ということで、こちらの記事は以上です。最後までお読みいただき、ありがとうございます。