0

How to use the custom hook useCustomHook() in Next.js and prevent the did not match server/client error without using useEffect()? Like when using swr for example.

useData() stores data on the client side (context + localStorage). I am trying to create a static site with some data provided by the user.

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>
}

Context provider (<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)

Will result in Warning: Text content did not match. Server: "No data..." Client: "Data " when I reload the page.

But when I use useSWR I can reload the page without getting an error. Also the request is done only on the client side, server side data is just undefined (server doesn't make a request at all). How does this work? I have checked the code from swr, but I don't get it.

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>
  );
}
1
  • Can you include the code from your useCustomContext too? Commented Apr 10, 2022 at 18:02

1 Answer 1

2

You want to memoize the return value in combination with useEffect() in your hook, that will also prevent multiple renders.

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
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, that worked! I didn't wanted to use useEffect because of rerender, but yeah, I have to use it and it doesn't rerender =) Thanks a lot.
The hasWindow might be redundant since useEffect only runs on the client and will always return true.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.