【react/next.js】useContextを使ってページのスクロール量を各コンポーネントで共通化するサンプルコード

概要

サイト内の各ページで使い回せるスクロール量を検出する関数(コンポーネント)の実装方法を解説します。

大まかな実装の流れは以下のようになります。

  1. スクロール量の値を格納するコンテキストを定義する(createContext)
  2. スクロール量を検知する関数を定義する(provider / useState / useEffect)
  3. 定義した関数をコンポーネントに組み込む(provider)
  4. スクロール量を取得して使用する(useContext)

propsを受け渡すことができるcontextを定義し、providerを経由し配下の子コンポーネントに渡す仕組みを構築します。

スクロール量を検出するコードはproviderの中にReact Hooks(useStateuseEffect)で記述します。

開発環境

next 14.0.1

実装コード

1.スクロール量の値を格納するコンテキストを定義する(createContext)

変数ScrollContextをコンテキストとして定義します。

import { createContext } from 'react'

// 初期値0でコンテキストを作成
export const ScrollContext = createContext(0)

※コンテキストとは
通常、親コンポーネントから子コンポーネントには props を使って情報を渡します。しかし、props を多数の中間コンポーネントを経由して渡さないといけない場合や、アプリ内の多くのコンポーネントが同じ情報を必要とする場合、props の受け渡しは冗長で不便なものとなり得ます。コンテクスト (Context) を使用することで、親コンポーネントから props を明示的に渡さずとも、それ以下のツリー内の任意のコンポーネントが情報を受け取れるようにできます。
https://ja.react.dev/learn/passing-data-deeply-with-context

2.スクロール量を検知する関数を定義する(provider / useState / useEffect)

ブラウザのスクロール量を検知・取得するコードを以下のように作成します。

import React, { useState, useEffect } from 'react'
import { ScrollContext } from '@/components/scrollContext'

export const ScrollProvider = ({ children }) => {
  
  // scrollYは、現在のスクロール位置を保持するための状態変数です。
  const [scrollY, setScrollY] = useState(0)

  // handleScrollはスクロールイベントが発生するたびに呼び出され、
  // 現在のスクロール位置(window.scrollY)をscrollY状態にセットします。
  const handleScroll = () => {
    setScrollY(window.scrollY)
  }

  // useEffectフックを使用して、コンポーネントのマウント時にスクロールイベントリスナーを設定し、
  // アンマウント時にはそれをクリーンアップ(削除)します。
  // これにより、不要なイベントリスナーが残らず、パフォーマンスの問題を防ぎます。
  useEffect(() => {
    window.addEventListener('scroll', handleScroll)
    return () => {
      window.removeEventListener('scroll', handleScroll)
    };
  }, []);

  // ScrollContext.Providerを使って、scrollY状態をコンテキストとして子コンポーネントに渡します。
  // これにより、子コンポーネントはこのコンテキストを利用してスクロール位置にアクセスできます。
  return (
    <ScrollContext.Provider value={scrollY}>
      {children}
    </ScrollContext.Provider>
  )
}

3.定義した関数をコンポーネントに組み込む(provider)

以下のように子コンポーネントをラップし、スクロール量の値を渡します。

import { ScrollProvider } from '@/components/scrollProvider'

export default function App({ Component, pageProps }) {
  const getLayout = Component.getLayout || ((page) => page) //レイアウト用コンポーネント
  return (
    <ScrollProvider>
      {getLayout(<Component {...pageProps} />)}
    </ScrollProvider>
  )
}

4.スクロール量を取得して使用する(useContext)

スクロール量の値を受け取り、描画ロジックのトリガーとして使います。

import React, {useState, useEffect, useContext} from 'react'
import { ScrollContext } from '@/components/scrollContext'

export default function Scroll() {
  const scrollY = useContext(ScrollContext); // ScrollContextからスクロール量を取得
  const isActive = scrollY > 100 // スクロール量が100pxを超えたらtrue
  return (
    <div className={`${isActive ? 'is-active' : ''}`}>
      <button>btn</button>
    </div>
  )
}