🤖

【Next.js】前後の記事リンクを出力を考察してみる その1

2022/05/29に公開

概要

前回、Next.jsでJSONファイルなど情報を取って来て、記事一覧と詳細ページを作成しました。

https://zenn.dev/kiriyama/articles/739fea7872a659

次なるステップとして記事一覧のページネーションと詳細ページでのページネーションを調べる中で今回は詳細ページでの「前の記事」と「次の記事」へのリンクの出力を考えてみる。

最終目的

今回の最終目標は以下とする

  • 「前の記事」と「次の記事」へのリンクができるようにする
  • 最初の記事と最後の記事にきたら、ボタンを非表示にする

どうすれば実装できるのかを考察してみる。

では今回、どのように記事の前後のリンクを実装しようか考えた時に、以下のデータが必要となる。

  • 前後のid番号
  • 記事の総数(次の記事が存在しない場合にボタンを非表示するのに使用する)

前後のid番号はgetStaticProps内で記事のid番号をcontext.params.idなどで取得できるので問題ないとして、記事の総数をどうやって取得しようと考えました。

詳細ページでJSONファイルから一覧を取得してるのでデータの総数を取得はできるものの、詳細ページの[id].jsファイルは親子関係にない為、propsuseContextなどで受け渡しができないと思った為です。

localStorageで実装してみる

調べた結果いくつか方法があったのだが、そのうちの一つの方法でlocalStorageがぱっと思いついたので試してみる。これが正しいかどうかは分からない。
ただ、ページを開いてる間は値が保持されてるし、保存したいデータは総数だけなので問題ないかなーと思ったり。

実際の流れ

では、実際の流れをメモしていく。
データを取得する方法などは以前の記事でメモってるので端折る。

また前回の記事のユーザー一覧と各ユーザーの詳細ページを例にメモる。
https://zenn.dev/kiriyama/articles/739fea7872a659

記事一覧ページから総数を取得してlocalStorageに保存

まずはユーザー一覧を表示するページでgetStaticPropsから取得して表示するソースコードは以下である。

users/index.js
export default function Users(data) {

  useEffect(()=>{
    localStorage.setItem("user_len",parseInt(data.user_len))
  },[])

  return (
    <div>
      <h1>JSONファイルを一覧表示</h1>
      <p>ユーザーの総数:{data.user_len}</p>
      <ul>
        // 取得したjsonデータをmap()で表示させる部分は省略
      </ul>
    </div>
  );
}

export const getStaticProps = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  const users = await res.json();
  const user_len = users.length;
  return {
    props: {
      users,
      user_len,
    },
  };
};

getStaticPropsでJSONファイルからユーザー一覧を取得したら、usrs.lengthで総数を取得してuser_lenという変数に入れて、propsの値に追加してあげる。
するとUsers(data)dataのプロパティにuser_lenが追加されるので、そこから総数の値を参照することができる。

その後、React HookであるuseEffectを利用して、読み込み時に1度だけ実行して、localStorageに総数をセットして総数を保存

useEffect(()=>{
    localStorage.setItem("user_len",parseInt(data.user_len))
  },[])

詳細ページから総数を取得してみる。

今度は詳細ページからlocalStorageで総数を取得する。

ユーザーの詳細を表示させる部分などは省略。
また[id].jsgetStaticPropsの場合はgetStaticPathsも必要となるが今回は関係ないのでこちらも省略してます。

pages/users/user/[id].js

import Link from "next/link";
import { useEffect,useState } from "react";

export default function User({user,prev,next }) {

  const [len,setLen]=useState(0)

  useEffect(()=>{
    setLen(localStorage.getItem("user_len"))
  },[])

  return (
    <div className="wrapper">
      //ここに各ユーザーの名前やアドレスなど表示する処理など
      
      <nav>
        {0<prev &&   
	<Link href={`/users/user/${prev}`}><a>前の記事</a></Link>
        }

        {len>=next &&
	<Link href={`/users/user/${next}`}><a>次の記事</a></Link>
        }
      </nav>
    </div>
  )
}

export const getStaticProps = async (context) => {
  const id = context.params.id
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
  const user = await res.json()
  const prev=parseInt(id)-1;
  const next=parseInt(id)+1;

  return {
    props: { 
      user,
      prev,
      next
     }
  }
}

一覧ページと同様に詳細ページでもgetStaticProps内のcontext.params.idで現在の記事のidを取得することができる。そのidのデータが文字列なのでparseIntで数値に変換して、以下のように前の記事と次の記事のidを取得する。その値をreturnpropsの値に追加してあげる。
これで一覧ページと同様に前後の記事のidを参照することができる。

const prev=parseInt(id)-1;
const next=parseInt(id)+1;

React HookのuseStateとuseEffectで総数を取得する

getStaticPropsで前後の記事idを設定したら、値を保存するためのuseStateとlocalStorageから記事総数を値を取得してセットするためのuseEffectを読み込み時に実行する。

 const [len,setLen]=useState(0)
  useEffect(()=>{
    setLen(localStorage.getItem("user_len"))
  },[])

前後の記事へのリンクを設定する

getStaticPropspropsに前後の記事のidを渡してるので、「前の記事」「次の記事」のボタンにリンクを設定してあげる。

<Link href={`/users/user/${prev}`}><a>前の記事</a></Link>
<Link href={`/users/user/${next}`}><a>次の記事</a></Link>

前後の記事へのリンクボタンを出し分けする。

localStorageから総数を取得できたら、記事の最初と最後になった場合に「前の記事」と「次の記事」のボタンの表示を切り替える必要があるのでその設定をする。

{0<prev &&   
   <Link href={`/users/user/${prev}`}><a>前の記事</a></Link>
 }
 {len>=next &&
  <Link href={`/users/user/${next}`}><a>次の記事</a></Link>
 }

上記のように「条件」の後に「&&」で繋げてあげて、「表示したい内容」と記述してあげると表示できる。上記は、prevの値が0より大きい場合(true)に「前の記事」ボタンを表示して、nextの値が記事の総数以下の場合(true)にLINKによるボタンを表示するという内容である。

&&演算子(論理積演算子、論理結合)という条件付きレンダーについて

三項演算子(条件式 ? true : false)は良く見るけど、&&演算子はあまり見ない?のでメモ。
参考にさせていただいたブログには以下のように記載されていました。

&&演算子は true && expressionならexpression、false && expressionならfalseを返します。

つまり、条件がtrueの場合は、&& の右側をレンダリングする。という意味らしい。

まとめ

これで「前の記事」と「次の記事」へのリンクができ、「最初の記事と最後の記事にきたら、ボタンを非表示にする」という事が実現できました。

今度は他の方法もあったのでそれを試してみようかと思います。

参考サイト

https://www.yoheim.net/blog.php?q=20180409
https://nishinatoshiharu.com/conditional-rendering/#i-4
https://monotein.com/blog/nextjs-blog-prev-next#前後の記事へのリンク

Discussion