Next.jsを少しずつ理解していく14【SSR+CSR|SSG+CSR】

今回はSSRとCSRを組み合わせてデータをフェッチする方法について紹介します。

この記事でやること

今回はユーザーの操作に応じて、表示する記事一覧ページの内容を更新できるページを実装していきます。

具体的には、ページの上部に用意したカテゴリ選択ボタンをユーザーがクリックし、クリックされたカテゴリに応じてCSR側でデータをフェッチしてレンダリングするような仕組みを構築します。

またそれだけだと、更新されたどの種類のカテゴリページでもURLが同じままになり、「知人とそのページをシェアする」といったことができなくなるので、URLも更新できるよう実装していきます。

SSRとCSRを組み合わせる

まずは、今回の記事で使用するデータを作成していきます。

ISRについて紹介した記事でも使用したjson-serverというパッケージを利用して、新たにフェッチするデータを作りましょう。

プロジェクトルートディレクトリ(pre-rendering)直下のdb.jsonを開いて、以下のデータを追加します。追加したらターミナルを開いて、npm run server-jsonコマンドを実行してください。

pre-rendering/db.json

"events": [
        {
            "id": 1,
            "title": "Event 1",
            "description": "Description 1",
            "category": "sports",
            "date": "April 25"
        },
        {
            "id": 2,
            "title": "Event 2",
            "description": "Description 2",
            "category": "technology",
            "date": "May 25"
        },
        {
            "id": 3,
            "title": "Event 3",
            "description": "Description 3",
            "category": "food",
            "date": "October 25"
        },
        {
            "id": 4,
            "title": "Event 4",
            "description": "Description 4",
            "category": "food",
            "date": "April 25"
        },
        {
            "id": 5,
            "title": "Event 5",
            "description": "Description 5",
            "category": "sports",
            "date": "January 15"
        },
        {
            "id": 6,
            "title": "Event 6",
            "description": "Description 6",
            "category": "art",
            "date": "April 25"
        },
        {
            "id": 7,
            "title": "Event 7",
            "description": "Description 7",
            "category": "technology",
            "date": "June 25"
        },
        {
            "id": 8,
            "title": "Event 8",
            "description": "Description 8",
            "category": "sports",
            "date": "February 28"
        },
        {
            "id": 9,
            "title": "Event 9",
            "description": "Description 9",
            "category": "food",
            "date": "April 25"
        },
        {
            "id": 10,
            "title": "Event 10",
            "description": "Description 10",
            "category": "sports",
            "date": "August 5"
        }
    ]

 

続いてはpagesディレクトリ直下にevents.jsファイルを作成して、getServerSideProps関数で記事データ(db.json)をフェッチし一覧表示できるようにしていきます。

Terminal

touch pre-rendering/pages/events.js

pre-rendering/pages/events.js

function EventList({eventList}){
    return(
        <>
            <h1>List of events</h1>
            {
                eventList.map(event => {
                    return(
                        <div key={event.id}>
                            <h2>
                                {event.id} {event.title} {event.date} | {event.category}
                            </h2>
                            <p>{event.description}</p>
                            <hr/>
                        </div>
                    )
                })
            }
        </>
    )
}

export default EventList

export async function getServerSideProps() {
    const response = await fetch('http://localhost:3001/events')
    const data = await response.json()

    return{
        props: {
            eventList: data,
        }
    }
}

結果

続いては、ページ上部に<button>タグを用意して、押したタグに応じて一覧表示される記事データを更新できるようにしていきます。

pre-rendering/pages/events.js

import {useState} from 'react'

function EventList({eventList}){
    const [events, setEvents] = useState(eventList)

    const fetchEvents = async (e) => {
        let eventName = e.currentTarget.dataset['index']
        const response = await fetch(`http://localhost:3001/events?category=${eventName}`)
        const data = await response.json()
        setEvents(data)
    }

    return(
        <>
            <button data-index="sports" onClick={fetchEvents}>Sports Events</button>
            <button data-index="technology" onClick={fetchEvents}>Technology Events</button>
            <button data-index="food" onClick={fetchEvents}>Food Events</button>
            <button data-index="art" onClick={fetchEvents}>Art Events</button>
            <h1>List of events</h1>
            {
                events.map(event => {
                    return(
                        <div key={event.id}>
                            <h2>
                                {event.id} {event.title} {event.date} | {event.category}
                            </h2>
                            <p>{event.description}</p>
                            <hr/>
                        </div>
                    )
                })
            }
        </>
    )
}

export default EventList

export async function getServerSideProps() {
    const response = await fetch('http://localhost:3001/events')
    const data = await response.json()

    return{
        props: {
            eventList: data,
        }
    }
}

結果

例えば「Sports Events」ボタンをクリックすると、sportsカテゴリの記事だけが表示されます。

datasetとは?

今回は、クリックされたbutton要素を識別するために、HTMLElement.datasetを使用しました。

使い方はシンプルで、識別したい要素にdata-xxx="○○○"を指定します。
クリックされた要素をJS側で識別する際は、currentTarget.dataset['xxx']と記述します。xxxの部分は任意に指定可能で、今回はindexとしました。

datasetには、HTML側でdata-xxxを指定したすべての要素が含まれているので、dataset['xxx']という形で値を指定する必要があります。

HTMLElement.dataset|MDN

CSRでURLも変更する

さて、クリックされた要素に応じて記事一覧を更新することはできましたが、1つ問題があります。例えば、sportsカテゴリの記事一覧ページを友人に見せたい場合、多くはメッセージアプリなどでURLを共有することで対応するでしょう。しかし先ほどの実装では、どのカテゴリの一覧ページもURLはlocalhost:3000/eventsのままです。

そこで、続いてはuseRouter Hooksを利用して、記事一覧を更新するとともにURLも変更できるようにしていきます。

pre-rendering/pages/events.js

import {useEffect, useState} from 'react'
import {useRouter} from 'next/router'

function EventList({eventList}){
    const [events, setEvents] = useState(eventList)
    const router = useRouter()

    const fetchEvents = async (e) => {
        const eventName = e.currentTarget.dataset['index']
        const response = await fetch(`http://localhost:3001/events?category=${eventName}`)
        const data = await response.json()
        setEvents(data)
        router.push(`/events?category=${eventName}`, undefined, { shallow: true})
    }

    return(
        <>
            <button data-index="sports" onClick={fetchEvents}>Sports Events</button>
            <button data-index="technology" onClick={fetchEvents}>Technology Events</button>
            <button data-index="food" onClick={fetchEvents}>Food Events</button>
            <button data-index="art" onClick={fetchEvents}>Art Events</button>
            <h1>List of events</h1>
            {
                events.map(event => {
                    return(
                        <div key={event.id}>
                            <h2>
                                {event.id} {event.title} {event.date} | {event.category}
                            </h2>
                            <p>{event.description}</p>
                            <hr/>
                        </div>
                    )
                })
            }
        </>
    )
}

export default EventList

export async function getServerSideProps(context) {
    const {query} = context
    const {category} = query
    const queryString = category ? `category=${category}` : ''
    const response = await fetch(`http://localhost:3001/events?${queryString}`)
    const data = await response.json()

    return{
        props: {
            eventList: data,
        }
    }
}

結果

まとめ

以上でSSRとCSRを組みあせたデータフェッチについての説明は終わりです。

 

次回はAPIルーティングについて紹介します。

コメントを残す

CAPTCHA