Skip to content

Commit

Permalink
fix docs, now we've got it right
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn committed Nov 14, 2024
1 parent 846fae8 commit fdfe5f3
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 59 deletions.
39 changes: 7 additions & 32 deletions docs/src/pages/docs/usage/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -451,64 +451,37 @@ const timeZone = await getTimeZone();

## Now value [#now]

When formatting [relative dates and times](/docs/usage/dates-times#relative-times), `next-intl` will format times in relation to a reference point in time that is referred to as "now". If you want to ensure that this value is consistent across components, you can configure a global `now` value:
When formatting [relative dates and times](/docs/usage/dates-times#relative-times), `next-intl` will format times in relation to a reference point in time that is referred to as "now". While it can be beneficial in terms of caching to [provide this value](/docs/usage/dates-times#relative-times-usenow) where necessary, you can provide a global value for `now`, e.g. to ensure consistency when running tests.

<Tabs items={['i18n/request.ts', 'Provider']}>
<Tabs.Tab>

```tsx filename="i18n/request.ts"
import {getRequestConfig} from 'next-intl/server';

async function now() {
// (if you're using `dynamicIO`)
// 'use cache';

// Use this value consistently
// when formatting relative times
return new Date();
}

export default getRequestConfig(async () => {
return {
now: await now()
now: new Date('2024-11-14T10:36:01.516Z')

// ...
};
});
```

If a `now` value is provided in `i18n/request.ts`, this will automatically be inherited by Client Components if you wrap them in a `NextIntlClientProvider` that is rendered by a Server Component.

<Details id="now-cache">
<summary>How does the usage of `'use cache'` with `now` relate to cache expiration?</summary>

If you're using [`dynamicIO`](https://nextjs.org/docs/canary/app/api-reference/config/next-config-js/dynamicIO), you can cache the value of `now` with the [`'use cache'`](https://nextjs.org/docs/canary/app/api-reference/directives/use-cache) directive:

```tsx
async function now() {
'use cache';
const now = new Date();
}
```

Since the request config from `i18n/request.ts` is shared among all Server Components that use features from `next-intl`, the cache expiration for `now` will apply to all of these, regardless of if they're using relative time formatting or not.

If you want more granular cache control, you can consider passing the `now` value to [`format.relativeTime`](/docs/usage/dates-times#relative-times) explicitly as a second argument where relevant.

</Details>

</Tabs.Tab>
<Tabs.Tab>

```tsx
const now = new Date();
const now = new Date('2024-11-14T10:36:01.516Z');

<NextIntlClientProvider now={now}>...</NextIntlClientProvider>;
```

</Tabs.Tab>
</Tabs>

If a `now` value is provided in `i18n/request.ts`, this will automatically be inherited by Client Components if you wrap them in a `NextIntlClientProvider` that is rendered by a Server Component.

### `useNow` & `getNow` [#use-now]

The configured `now` value can be read in components via `useNow` or `getNow`:
Expand All @@ -523,6 +496,8 @@ import {getNow} from 'next-intl/server';
const now = await getNow();
```

Note that the returned value defaults to the current date and time, therefore making this hook useful when [providing `now`](/docs/usage/dates-times#relative-times-usenow) for `format.relativeTime` even when you haven't configured a global `now` value.

## Formats

To achieve consistent date, time, number and list formatting, you can define a set of global formats.
Expand Down
70 changes: 61 additions & 9 deletions docs/src/pages/docs/usage/dates-times.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,72 @@ function Component() {

Note that values are rounded, so e.g. if 126 minutes have passed, "2 hours ago" will be returned.

### Providing `now` [#relative-times-now]
### `useNow` [#relative-times-usenow]

The `now` value can either be provided on a case-by-case basis or configured [globally](/docs/usage/configuration#now).
Since providing `now` is a common pattern, `next-intl` provides a convenience hook that can be used to retrieve the current date and time:

If you've configured a global `now` value, you can omit the corresponding parameter:
```tsx {4}
import {useNow, useFormatter} from 'next-intl';

```js
// Uses the global now value
format.relativeTime(dateTime);
function FormattedDate({date}) {
const now = useNow();
const format = useFormatter();

format.relativeTime(date, now);
}
```

In contrast to simply calling `new Date()` in your component, `useNow` has some benefits:

1. The returned value is consistent across re-renders.
2. The value can optionally be [updated continuously](#relative-times-update) based on an interval.
3. The value can optionally be initialized from a [global value](/docs/usage/configuration#now), e.g. allowing you to use a static `now` value to ensure consistency when running tests.

<Details id="relative-times-hydration">
<summary>How can I avoid hydration errors with `useNow`?</summary>

If you're using `useNow` in a component that renders both on the server as well as the client, you can consider using [`suppressHydrationWarning`](https://react.dev/reference/react-dom/client/hydrateRoot#suppressing-unavoidable-hydration-mismatch-errors) to tell React that this particular text is expected to potentially be updated on the client side:

```tsx {7}
import {useNow, useFormatter} from 'next-intl';

function FormattedDate({date}) {
const now = useNow();
const format = useFormatter();

return <span suppressHydrationWarning>{format.relativeTime(date, now)}</span>;
}
```

### Continuously updating relative times [#relative-times-update]
While this prop has a somewhat intimidating name, it's an escape hatch that was purposefully designed for cases like this.

</Details>

<Details id="relative-times-server">
<summary>How can I use `now` in Server Components with `dynamicIO`?</summary>

If you're using [`dynamicIO`](https://nextjs.org/docs/canary/app/api-reference/config/next-config-js/dynamicIO), Next.js may prompt you to specify a cache expiration in case you're using `useNow` in a Server Component.

You can do so by annotating your component with the `'use cache'` directive, while converting it to an async function:

```tsx
import {getNow, getFormatter} from 'next-intl/server';

async function FormattedDate({date}) {
'use cache';

const now = await getNow();
const format = await getFormatter();

return format.relativeTime(date, now);
}
```

</Details>

### `updateInterval` [#relative-times-update]

In case you want a relative time value to update over time, you can do so with [the `useNow` hook](/docs/usage/configuration#now):
In case you want a relative time value to update over time, you can do so with [the `useNow` hook](/docs/usage/configuration#use-now):

```js
import {useNow, useFormatter} from 'next-intl';
Expand All @@ -110,7 +162,7 @@ function Component() {
}
```

### Customizing the unit
### Customizing the unit [#relative-times-unit]

By default, `relativeTime` will pick a unit based on the difference between the passed date and `now` like "3 seconds" or "5 days".

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {cache} from 'react';

function defaultNow() {
// See https://next-intl-docs.vercel.app/docs/usage/dates-times#relative-times
// See https://next-intl-docs.vercel.app/docs/usage/dates-times#relative-times-server
return new Date();
}

Expand Down
2 changes: 1 addition & 1 deletion packages/use-intl/src/core/createFormatter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export default function createFormatter(props: Props) {
function relativeTime(
/** The date time that needs to be formatted. */
date: number | Date,
/** The reference point in time to which `date` will be formatted in relation to. */
/** The reference point in time to which `date` will be formatted in relation to. If this value is absent, a globally configured `now` value or alternatively the current time will be used. */
nowOrOptions?: RelativeTimeFormatOptions['now'] | RelativeTimeFormatOptions
) {
try {
Expand Down
17 changes: 1 addition & 16 deletions packages/use-intl/src/react/useNow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,7 @@ function getNow() {
}

/**
* Reading the current date via `new Date()` in components should be avoided, as
* it causes components to be impure and can lead to flaky tests. Instead, this
* hook can be used.
*
* By default, it returns the time when the component mounts. If `updateInterval`
* is specified, the value will be updated based on the interval.
*
* You can however also return a static value from this hook, if you
* configure the `now` parameter on the context provider. Note however,
* that if `updateInterval` is configured in this case, the component
* will initialize with the global value, but will afterwards update
* continuously based on the interval.
*
* For unit tests, this can be mocked to a constant value. For end-to-end
* testing, an environment parameter can be passed to the `now` parameter
* of the provider to mock this to a static value.
* @see https://next-intl-docs.vercel.app/docs/usage/dates-times#relative-times-usenow
*/
export default function useNow(options?: Options) {
const updateInterval = options?.updateInterval;
Expand Down

0 comments on commit fdfe5f3

Please sign in to comment.