Skip to content

Commit

Permalink
fix: Hydration Error (#1)
Browse files Browse the repository at this point in the history
This was an annoying little bug. On certain versions of Node.js the
function `Date.prototype.toLocaleString()` does not use a regular
unicode space for the seperator, but rather a U+202F Narrow No-Break
Space.

```
> Buffer.from('8:30 AM')
<Buffer 38 3a 33 30 e2 80 af 41 4d>

// E280AF is Narrow No-Break Space!
// https://codepoints.net/U+202F

> Buffer.from('8:30 AM')
<Buffer 38 3a 33 30 20 41 4d>
```

This is only true for the Node.js side though, not the browser rendered
version. So when then client renders the string it uses a normal space,
resulting in a hydration error mismatch.

nodejs/node#46123

There are two ways to fix this.

1. Clean the string on the server side by removing the unusual
   whitespace.
2. Only render the time on the browser

While option 1 seems okay initially, it's open to new issues in the
future. The truth is that the code can't make assumptions about how the
string will appear and we are rendering it on two differnt platforms,
Node.js and the client browser. Different browsers may render the string
even more differently and there will still be a mismatch. V8 vs Gecko
for example.

Therefore, the best solution is to just render the final string on the
client. To do so, we move the only the time rendering component into
it's own file and dynamically import it. Also turn server side rendering
off.

This isn't ideal, but because of the complexity of strings and dates,
it's the only way to ensure there is no hydration mismatch.

Reported by Sivanth_P http://www.youtube.com/channel/UCXWFDIEFQwbIzxU-nkHV90w

Thanks!
  • Loading branch information
ethanmick authored Feb 23, 2023
1 parent 9a7ae92 commit 957d18b
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 4 deletions.
8 changes: 4 additions & 4 deletions app/clock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use client'

import dynamic from 'next/dynamic'
import { useEffect, useState } from 'react'

const Time = dynamic(() => import('./time'), { ssr: false })

type Props = {
time: number
}
Expand All @@ -19,10 +22,7 @@ export const Clock = ({ time: initial }: Props) => {

return (
<div className="text-7xl tabular-nums">
{time.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit'
})}
<Time time={time} />
</div>
)
}
15 changes: 15 additions & 0 deletions app/time.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
type Props = {
time: Date
}
const Time = ({ time }: Props) => {
return (
<span>
{time.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit'
})}
</span>
)
}

export default Time

0 comments on commit 957d18b

Please sign in to comment.