Skip to content

Commit

Permalink
some wording improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn committed Sep 18, 2024
1 parent 97c8159 commit 0536c32
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 18 deletions.
41 changes: 24 additions & 17 deletions docs/pages/blog/date-formatting-nextjs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Tweet} from 'react-tweet';

# Safe date formatting in Next.js

<small>Sep 18, 2024 · by Jan Amann</small>
<small>Sep 19, 2024 · by Jan Amann</small>

Let's consider the following component:

Expand All @@ -20,21 +20,31 @@ type Props = {
export default function BlogPostPublishedDate({published}: Props) {
const now = new Date();

// "1 hour ago"
// ... is this ok? 🤔
return <p>{formatDistance(published, now, {addSuffix: true})}</p>;
}
```

A first local test of this component renders the expected result: "1 hour ago".

But can this component potentially cause issues in a Next.js app? Let's have a look together.

## Environment differences

Since this component doesn't use any interactive features of React like `useState`, it can be considered a [shared component](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#sharing-code-between-server-and-client). In practice, this means that depending on where the component is being imported from, it may render as either a Server Component or a Client Component.

Now, let's consider where the component renders based on the environment:
Now, let's consider where the component renders in either case:

| Type | Server | Client |
| ---------------- | ------ | ------ |
| Server Component || |
| Client Component |||

Since the server and the client likely have a different local time when the component is rendered, we can already assume that this component may ask for trouble.
Now the good news is that if we render this component as a Server Component, there's only one environment to consider: _the server_. The final markup is generated there, and this is what the user will see.

When it comes to Client Components, the situation is a bit more complex. Since the server and the client likely have a different local time when the component is rendered, we can already anticipate that this component may render inconsistently depending on the environment.

There's some interesting nuance here as well: Since our component qualifies as a shared component, it can run in either environment. Even if the developer originally intended the component to run as a Server Component, it may silently switch to a Client Component if it gets imported into a Client Component in the future—leading to the additional rendering environment to consider.

## Hydration mismatches

Expand Down Expand Up @@ -75,20 +85,14 @@ Quoting from the React docs, a pure function has these two characteristics:
> **It minds its own business:** It does not change any objects or variables that existed before it was called.
> **Same inputs, same output:** Given the same inputs, a pure function should always return the same result.
Since the component is reading from the constantly changing `new Date()` constructor during rendering, it violates the principle of "same inputs, same output—leading to unpredictable behavior. React components require functional purity to ensure consistent output when being re-rendered (which can happen at any time and often without explicit user interaction).
Since the component is reading from the constantly changing `new Date()` constructor during rendering, it violates the principle of "same inputs, same output. React components require functional purity to ensure consistent output when being re-rendered (which can happen at any time and often without explicit user interaction).

But is this true for all components? In fact, with the introduction of Server Components, there's a new type of component in town that doesn't have the restriction of "same inputs, same output". Server Components can for instance fetch data, making their output reliant on the state of an external system. This is fine, because Server Components only generate an output once—_on the server_.

So what does this mean for our component?

## Leveraging Server Components

In fact, in case our component only renders as a Server Component, we're good to go. The `now` variable will be generated on the server, and we won't run into any issues with hydration mismatches.

Note however that we've implemented our component as a "shared component", meaning that even if it was originally intended to run as a Server Component, the component will silently switch to a Client Component if it gets imported into a Client Component in the future.

How can we solve this issue if we want `BlogPostPublishedDate` to also work reliably as a Client Component?

Right, you may have guessed it: We can move the creation of the `now` variable to a Server Component and pass it down as a prop.

```tsx filename="page.tsx"
Expand All @@ -112,9 +116,9 @@ Are we done yet?

## What time is it?

What if we have more components that rely on the current time? We could instantiate the `now` variable in each component that needs it, but if you consider that even during a single render pass there can be timing differences, this might result in inconsistencies. This might be significant in case you have asynchronous operations like data fetching.
What if we have more components that rely on the current time? We could instantiate the `now` variable in each component that needs it, but if you consider that even during a single render pass there can be timing differences, this might result in inconsistencies. This might be significant in case you have asynchronous operations like data fetching that delay the rendering of certain components.

An option to ensure that a single `now` value is used across all components that render as part of a single request is to use the [`cache()`](https://react.dev/reference/react/cache) functionality from React:
An option to ensure that a single `now` value is used across all components that render as part of a single request is to use the [`cache()`](https://react.dev/reference/react/cache) function from React:

```tsx filename="getNow.ts"
import {cache} from 'react';
Expand All @@ -130,6 +134,7 @@ export default getNow;
import getNow from './getNow';

export default function BlogPost() {
// ✅ Will be consistent for the current request
const now = getNow();
// ...
}
Expand All @@ -141,9 +146,9 @@ Well, are we done now?

## Where are we?

We've carefully ensured that our app is free of hydration mismatches and have established a consistent time handling across all components. But what happens if we decide one day that we don't want to render a relative time like "2 days ago", but a specific date like "Sep 18, 2024"?
We've carefully ensured that our app is free of hydration mismatches and have established a consistent time handling across all components. But what happens if we decide one day that we don't want to render a relative time like "2 days ago", but a specific date like "Sep 19, 2024"?

```tsx
```tsx {8,9}
import {format} from 'date-fns';

type Props = {
Expand Down Expand Up @@ -226,6 +231,8 @@ export default function BlogPostPublishedDate({
}
```

It's important to pass the `locale` to all formatting calls now, as this can differ by environment—just like the `timeZone` and our value for `now` from earlier.

## Can `next-intl` help?

Since you're reading this post on the `next-intl` blog, you've probably already guessed that we have an opinion on this subject. Note that this is not at all a critizism of libraries like `date-fns` & friends. On the contrary, I can only recommend these packages e.g. for manipulating dates.
Expand Down Expand Up @@ -264,9 +271,9 @@ export default function BlogPostPublishedDate({published}: Props) {

Note that as the name of `getRequestConfig` implies, the configuration object can be created per request, allowing for dynamic configuration based on the user's preferences.

## Further reading
## Related resources

While the main focus of this post was date formatting, there are a few related resources that I can recommend if you're interested in
While the main focus of this post was date formatting, there are a few related resources that I can recommend if you're interested to dive deeper into the topic:

1. [API and JavaScript Date Gotcha's](https://www.solberg.is/api-dates) by [Jökull Solberg](https://x.com/jokull)
2. Manipulating dates with [`date-fns`](https://date-fns.org/v4.1.0/docs/addDays) (e.g. [`addDays`](https://date-fns.org/v4.1.0/docs/addDays))
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/blog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import CommunityLink from 'components/CommunityLink';
<CommunityLink
href="/blog/date-formatting-nextjs"
title="Safe date formatting in Next.js"
date="Sep 18, 2024"
date="Sep 19, 2024"
author="By Jan Amann"
/>
<CommunityLink
Expand Down

0 comments on commit 0536c32

Please sign in to comment.