5

I'm building an application as a hobby project and as an effort to try and learn server rendered React, but I've stumbled on a seemingly easy to fix error, but I do not know how I should approach the problem. Using Remix 1.10.

While my code runs, it is flawed. The server renders one thing and the client another, causing the rendered element to flicker on pageload. It also throws a multitude of errors in the console, like:

Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

24x react-dom.development.js:12507 Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.

react_devtools_backend.js:4012 Warning: Text content did not match. Server: "1/29/2023, 10:44:09 AM" Client: "1/29/2023, 12:44:09 PM"

The server is on UTC timezone but the client can be anything. In this case it's GMT+2. What should I do? I think I could set the server timezone to what the client timezone is but I also think that might be a terrible idea.

The best barebones dumbed down example I could make is this.

// routes/example.tsx
import { useLoaderData } from "@remix-run/react"
import {json, LoaderArgs } from "@remix-run/server-runtime"

export async function loader({ request }: LoaderArgs) {
  const timestampFromDB = "2023-01-29T10:44:09.672Z"
  
  return json({ time: timestampFromDB })
}

export default function HydrationError() {
  const loaderData = useLoaderData<typeof loader>()
  const time = new Date(loaderData.time)
  const stamp = time.toLocaleString("en-US")

  return (
    <div>
      Time: 

      <time>{stamp}</time>
    </div>
  )
}

I tried to look for answers before asking, but the closest thing I found isn't even close to what my problem is; Remix Hydration failed: UI on server and client do not match. In my case, it's not fine locally, it's not fine at all.

Alex
  • 51
  • 2
  • Is this a duplicate of this one? (Does the issue still appear if you try in incognito? It might be caused by some of your browser extensions) https://stackoverflow.com/questions/75333351/why-does-the-project-initialized-with-remixs-indie-stack-get-the-error-hydratio – lajtmaN Feb 07 '23 at 16:30

2 Answers2

1

The toLocaleString spec allows output variations across implementations so you're probably better off avoiding the client's implementation and just using the server's implementation by moving toLocaleString to the loader.

// routes/example.tsx
import { useLoaderData } from "@remix-run/react"
import {json, LoaderArgs } from "@remix-run/server-runtime"

export async function loader({ request }: LoaderArgs) {
  const timestampFromDB = "2023-01-29T10:44:09.672Z"
  
  return json({ stamp: new Date(timestampFromDB).toLocaleString('en-US') })
}

export default function HydrationError() {
  const { stamp } = useLoaderData<typeof loader>()

  return (
    <div>
      Time: 

      <time>{stamp}</time>
    </div>
  )
}

Alternatively you might want to look at Intl.DateTimeFormat which gives you greater control over date rendering and may offer more consistency.

React Intl is a library built on top of Intl.DateTimeFormat which is worth checking out.

Richard Scarrott
  • 6,638
  • 1
  • 35
  • 46
  • Unfortunately in the real use case I have dozens of date objects and multiple uses of all, in different formats. I'd have to pass quite a few locale strings through the loader. That doesn't seem like a good way to go. I'll look into Intl.DateTimeFormat! – Alex Feb 02 '23 at 22:44
0

I faced the same issue and this is how I solved it.

  1. create a local state for the time string
  2. set the string using useEffect so that it is only rendered inside the browser.
export default function HydrationError() {
  const loaderData = useLoaderData<typeof loader>()
  const [stamp, setStamp] = useState("");

  useEffect(() => {
    setStamp(new Date(loaderData.time).toLocaleString());
  }, [loaderData.time]);

  return (
    <div>
      Time: 

      <time>{stamp}</time>
    </div>
  )
}
Gaurish Gangwar
  • 395
  • 4
  • 14