Next.js 为客户端自定义挂钩并防止 "did not match server/client" 错误

Next.js custom hook for client side and prevent "did not match server/client" error

如何在Next.js中使用自定义钩子useCustomHook()并在不使用useEffect()的情况下防止did not match server/client错误?例如使用 swr 时。

useData() 在客户端存储数据(context + localStorage)。 我正在尝试使用用户提供的一些数据创建一个静态站点。

function useCustomHook() {
  const [{ data }, dispatch] = useData()

  if (typeof window !== 'undefined') { // Client side
    return { data: data }
  }

  return { data: undefined }
}

export default function Page() {
  const { data } = useCustomHook()

  if (!data) {
    return <div>No data...</div>
  }

  return <div>Data {data.foobar}</div>
}

上下文提供者(<DataProvider>@_app.js

import { useReducer, useContext, createContext } from 'react'

const DataStateContext = createContext()

const initialState = {
  data: {},
}

const reducer = (state, action) => {
  switch (action.type) {
    default:
      throw new Error(`Unknown action: ${action.type}`)
  }
}

export const DataProvider = ({ children }) => {
  const localState = initialState

  // Get the data from localStorage
  try {
    localState.data = localStorage.getItem('data')
      ? JSON.parse(localStorage.getItem('data'))
      : null
  } catch (err) {}

  const [state, dispatch] = useReducer(reducer, localState)

  return (
    <DataStateContext.Provider value={[state, dispatch]}>
      {children}
    </DataStateContext.Provider>
  )
}

export const useData = () => useContext(DataStateContext)

当我重新加载页面时将导致 Warning: Text content did not match. Server: "No data..." Client: "Data "

但是当我使用 useSWR 时,我可以重新加载页面而不会出现错误。此外,请求仅在客户端完成,服务器端 data 只是未定义(服务器根本不发出请求)。这是如何运作的?我已经检查了 swr 的代码,但我不明白。

import useSWR from "swr";

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function App() {
  const { data, error } = useSWR(
    "https://api.github.com/repos/vercel/swr",
    fetcher
  );

  if (error) return "An error has occurred.";
  if (!data) return "Loading...";
  return (
    <div>{data.description}</div>
  );
}

您想在挂钩中结合 useEffect() 记住 return 值,这也将防止多次渲染。

function useCustomHook() {
  const [{ data }, dispatch] = useData()
  const [clientData, setClientData] = useState()

  const hasWindow = typeof window !== 'undefined'

  useEffect(() => {
    if (hasWindow) {
      setClientData(data)
    }
  }, [hasWindow, data])

  const value = useMemo(
    () => ({
      data: clientData,
    }),
    [clientData]
  )

  return value
}