Skip to content

Commit

Permalink
feat: simplify provider inheritance, add new APIs (#1387)
Browse files Browse the repository at this point in the history
Also introduces `createIntl` and `RawIntlProvider`
fixes #1386 
fixes #1376 

## Creating intl without using Provider

We've added a new API called `createIntl` that allows you to create an `IntlShape` object without using `Provider`. This allows you to format things outside of React lifecycle while reusing the same `intl` object. For example:

```tsx
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'

// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()

const intl = createIntl({
  locale: 'fr-FR',
  messages: {}
}, cache)

// Call imperatively
intl.formatNumber(20)

// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
```

This is especially beneficial in SSR where you can reuse the same `intl` object across requests.
  • Loading branch information
longlho authored Aug 2, 2019
1 parent 05970c2 commit 227a444
Show file tree
Hide file tree
Showing 22 changed files with 295 additions and 289 deletions.
25 changes: 25 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ There are a few API layers that React Intl provides and is built on. When using
- [`useIntl` hook (currently available in 3.0.0 beta)](#useintl-hook-currently-available-in-300-beta)
- [`injectIntl` HOC](#injectintl-hoc)
- [`IntlShape`](#intlshape)
- [`createIntl`](#createintl)
- [Date Formatting APIs](#date-formatting-apis)
- [`formatDate`](#formatdate)
- [`formatTime`](#formattime)
Expand Down Expand Up @@ -86,6 +87,7 @@ React Intl provides:

1. [`useIntl` hook](#useintl-hook): to _hook_ the imperative formatting API into a React function component (with React version >= 16.8).
2. [`injectIntl` HOC](#injectintl-hoc): to _inject_ the imperative formatting API into a React class or function component via its `props`.
3. [`createIntl`](#createintl): to create `IntlShape` object outside of React lifecycle.

These should be used when your React component needs to format data to a string value where a React element is not suitable; e.g., a `title` or `aria` attribute, or for side-effect in `componentDidMount`.

Expand Down Expand Up @@ -198,6 +200,29 @@ The definition above shows what the `props.intl` object will look like that's in
- **`IntlConfig`:** The intl metadata passed as props into the parent `<IntlProvider>`.
- **`IntlFormatters`:** The imperative formatting API described below.

#### `createIntl`

This allows you to create an `IntlShape` object without using `Provider`. This allows you to format things outside of React lifecycle while reusing the same `intl` object. For example:

```tsx
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'

// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()

const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)

// Call imperatively
intl.formatNumber(20)

// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
```

### Date Formatting APIs

React Intl provides three functions to format dates:
Expand Down
22 changes: 19 additions & 3 deletions docs/Components.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ React Intl has a set of React components that provide a declarative way to setup
- [Why Components?](#why-components)
- [Intl Provider Component](#intl-provider-component)
- [`IntlProvider`](#intlprovider)
- [Multiple Intl Contexts](#multiple-intl-contexts)
- [`RawIntlProvider`](#rawintlprovider)
- [Dynamic Language Selection](#dynamic-language-selection)
- [Date Formatting Components](#date-formatting-components)
- [`FormattedDate`](#formatteddate)
Expand Down Expand Up @@ -109,9 +109,25 @@ Assuming `navigator.language` is `"fr"`:
<div><span>mardi 5 avril 2016</span></div>
```

#### Multiple Intl Contexts
### `RawIntlProvider`

Nested `<IntlProvider>` components can be used to provide a different, or modified i18n context to a subtree of the app. In these cases, the nested `<IntlProvider>` will inherit from its nearest ancestor `<IntlProvider>`. A nested strategy can be employed to provide a subset of translations to a subtree. See: [Nested Example app](/~https://github.com/formatjs/react-intl/tree/master/examples/nested)
This is the underlying `React.Context.Provider` object that `IntlProvider` use. It can be used in conjunction with `createIntl`:

```tsx
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'

// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()

const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)

// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
```

#### Dynamic Language Selection

Expand Down
8 changes: 4 additions & 4 deletions docs/Testing-with-React-Intl.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ import expect from 'expect';
import expectJSX from 'expect-jsx';
import React from 'react';
import {createRenderer} from 'react-addons-test-utils';
import {IntlProvider, FormattedRelative, generateIntlContext} from 'react-intl';
import {IntlProvider, FormattedRelative, createIntl} from 'react-intl';
import RelativeDate from '../relative-date';

expect.extend(expectJSX);
Expand All @@ -146,7 +146,7 @@ describe('<RelativeDate>', function() {
const renderer = createRenderer();
const date = new Date();

const intl = generateIntlContext({
const intl = createIntl({
locale: 'en',
defaultLocale: 'en',
});
Expand Down Expand Up @@ -252,13 +252,13 @@ Testing with Enzyme works in a similar fashion as written above. Your `mount()`e
*/

import React from 'react';
import {IntlProvider, generateIntlContext} from 'react-intl';
import {IntlProvider, createIntl} from 'react-intl';
import {mount, shallow} from 'enzyme';

// You can pass your messages to the IntlProvider. Optional: remove if unneeded.
const messages = require('../locales/en'); // en.json

const intl = generateIntlContext({
const intl = createIntl({
locale: 'en',
defaultLocale: 'en',
messages,
Expand Down
29 changes: 28 additions & 1 deletion docs/Upgrade-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [Jest](#jest)
- [webpack babel-loader](#webpack-babel-loader)
- [Apostrophe Escape](#apostrophe-escape)
- [Creating intl without using Provider](#creating-intl-without-using-provider)

<!-- tocstop -->

Expand All @@ -32,7 +33,8 @@

- `FormattedRelative` has been renamed to `FormattedRelativeTime` and its API has changed significantly. See [FormattedRelativeTime](#formattedrelativetime) for more details.
- `formatRelative` has been renamed to `formatRelativeTime` and its API has changed significantly. See [FormattedRelativeTime](#formattedrelativetime) for more details.
- Escape character has been changed to apostrophe (`'`). See [Apostrophe Escape](#apostrophe-escape) for more details
- Escape character has been changed to apostrophe (`'`). See [Apostrophe Escape](#apostrophe-escape) for more details.
- `IntlProvider` no longer inherits from upstream `IntlProvider`.

## Use React 16.3 and upwards

Expand Down Expand Up @@ -350,3 +352,28 @@ Previously while we were using ICU message format syntax, our escape char was ba
```

We highly recommend reading the spec to learn more about how quote/escaping works [here](http://userguide.icu-project.org/formatparse/messages) under **Quoting/Escaping** section.

## Creating intl without using Provider

We've added a new API called `createIntl` that allows you to create an `IntlShape` object without using `Provider`. This allows you to format things outside of React lifecycle while reusing the same `intl` object. For example:

```tsx
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'

// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()

const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)

// Call imperatively
intl.formatNumber(20)

// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
```

This is especially beneficial in SSR where you can reuse the same `intl` object across requests.
26 changes: 8 additions & 18 deletions src/components/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {formatMessage as baseFormatMessage} from '../format';
import {
invariantIntlContext,
DEFAULT_INTL_CONFIG,
createDefaultFormatters,
createFormatters,
} from '../utils';
import {PrimitiveType, FormatXMLElementFn} from 'intl-messageformat/core';

Expand All @@ -35,7 +35,7 @@ const defaultFormatMessage = (
...DEFAULT_INTL_CONFIG,
locale: 'en',
},
createDefaultFormatters(),
createFormatters(),
descriptor,
values as any
);
Expand Down Expand Up @@ -67,22 +67,12 @@ export class BaseFormattedMessage<
}

shouldComponentUpdate(nextProps: Props<V>) {
const {values} = this.props;
const {values: nextValues} = nextProps;

if (!shallowEquals(nextValues, values)) {
return true;
}

// Since `values` has already been checked, we know they're not
// different, so the current `values` are carried over so the shallow
// equals comparison on the other props isn't affected by the `values`.
let nextPropsToCheck = {
...nextProps,
values,
};

return !shallowEquals(this.props, nextPropsToCheck);
const {values, ...otherProps} = this.props;
const {values: nextValues, ...nextOtherProps} = nextProps;
return (
!shallowEquals(nextValues, values) ||
!shallowEquals(otherProps, nextOtherProps)
);
}

render() {
Expand Down
Loading

0 comments on commit 227a444

Please sign in to comment.