Next.js - LocalStorageを使用したカウンターの作り方

1. カウンターを作成する

はじめにReactHookのuseStateを使用してシンプルなカウンターを作成します。

import { useState } from 'react'

const Home = () => {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>UP</button>
      <button onClick={() => setCount(count - 1)}>DOWN</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  )
}
export default Home

2. カウンターの値をLocalStorageに保存して取り出す

上記のままだとブラウザのリロードなどでカウンターの値がリセットされてしまいます。ブラウザのリロードなどをしてもカウンターの値を維持できるようにlocalStorageにカウンターの値を保存してそれを取り出す処理をしてみようと思います。localStorageを扱うためにここではuseEffectを使用します。ついでにリロードボタンとlocalStorageの中身を削除するボタンも追加します。

import { useState, useEffect } from 'react'

const Home = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setCount(Number(localStorage.getItem('count')))
  }, [setCount])

  useEffect(() => {
    localStorage.setItem('count', JSON.stringify(count))
  }, [count])

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => location.reload()}>Reload</button>
      <button onClick={() => setCount(count + 1)}>UP</button>
      <button onClick={() => setCount(count - 1)}>DOWN</button>
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => localStorage.removeItem('count')}>Remove</button>
      <button onClick={() => localStorage.clear()}>Clear</button>
    </div>
  )
}
export default Home

location.reload() - ブラウザのリロード
localStorage.removeItem('key') - ローカルストレージから指定したキーを削除
localStorage.clear() - ローカルストレージの中身を全削除

3. 一瞬のちらつき問題

上記のままでも機能としては正常に動作しますが、ローカルストレージの値を読み込むときに一瞬ちらついてしまうという問題があります。useEffectのちらつきを無くしたいときの対処法という記事を読んでuseLayoutEffectを使用してみました。useLayoutEffectを使用するとコンソールに下記のような警告が出てきました。

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.

useLayoutEffect and server rendering · GitHubに警告をスルーするやり方が書いてあったので実装してみたのが下記になります。一応警告も出ず、ちらつき問題も軽減できているようです。

import { useState, useEffect, useLayoutEffect } from 'react'

//useLayoutEffectの警告回避
const useBrowserLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : () => {}

const Home = () => {
  const [count, setCount] = useState(0)

  useBrowserLayoutEffect(() => {
    setCount(Number(localStorage.getItem('count')))
  }, [setCount])

  useEffect(() => {
    localStorage.setItem('count', JSON.stringify(count))
  }, [count])

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => location.reload()}>Reload</button>
      <button onClick={() => setCount(count + 1)}>UP</button>
      <button onClick={() => setCount(count - 1)}>DOWN</button>
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => localStorage.removeItem('count')}>Remove</button>
      <button onClick={() => localStorage.clear()}>Clear</button>
    </div>
  )
}
export default Home