Skip to content

Commit

Permalink
feat: make formatMessage take in ReactElement (#1367)
Browse files Browse the repository at this point in the history
Allow using `formatMessage` with `ReactElement` imperatively, e.g:
```tsx
formatMessage('hello {world}', {world: <b>world</b>)
// returns ['hello ', <b>world</b>]
```

New signature will be:
```ts
formatMessage(msg: string, values: Record<string, string | number | null | undefined | React.ReactNode>): string[] | React.ReactNodeArray
```

Use case:
- Right now the only way to format rich text is to use `FormattedMessage`, this brings feature-parity to the imperative version `formatMessage` as well. 
- `formatMessage` is also faster and doesn't create extra React Node.
- Also remove the need to regen UUID for every rich text token in `FormattedMessage`, which improves perf as well
Before:
```
100 x <FormattedMessage> with placeholder x 76.83 ops/sec ±9.83% (69 runs sampled)
100 x <FormattedMessage> with placeholder in AST form x 206 ops/sec ±1.12% (79 runs sampled)
100 x <FormattedMessage> with placeholder, cached x 573 ops/sec ±1.07% (84 runs sampled)
100 x <FormattedMessage> with placeholder, cached in AST form x 392 ops/sec ±4.28% (82 runs sampled)
```
After:
```
100 x <FormattedMessage> with placeholder x 85.51 ops/sec ±3.66% (66 runs sampled)
100 x <FormattedMessage> with placeholder in AST form x 210 ops/sec ±2.27% (76 runs sampled)
100 x <FormattedMessage> with placeholder, cached x 619 ops/sec ±2.00% (87 runs sampled)
100 x <FormattedMessage> with placeholder, cached in AST form x 426 ops/sec ±1.61% (84 runs sampled)
```
  • Loading branch information
longlho authored Jul 12, 2019
1 parent 20d39e6 commit 15ed625
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 170 deletions.
25 changes: 22 additions & 3 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,16 @@ Above, "source" refers to using the template as is, without any substitutions ma

#### `formatMessage`

```js
```ts
type MessageFormatPrimitiveValue = string | number | boolean | null | undefined;
function formatMessage(
messageDescriptor: MessageDescriptor,
values?: object
descriptor: MessageDescriptor,
values?: Record<string, MessageFormatPrimitiveValue>
): string;
function formatMessage(
descriptor: MessageDescriptor,
values?: Record<string, MessageFormatPrimitiveValue | React.ReactElement>
): string | React.ReactNodeArray;
```

This function will return a formatted message string. It expects a `MessageDescriptor` with at least an `id` property, and accepts a shallow `values` object which are used to fill placeholders in the message.
Expand All @@ -439,6 +444,20 @@ const messages = defineMessages({
formatMessage(messages.greeting, {name: 'Eric'}); // "Hello, Eric!"
```

with `ReactElement`

```tsx
const messages = defineMessages({
greeting: {
id: 'app.greeting',
defaultMessage: 'Hello, {name}!',
description: 'Greeting to welcome the user to the app',
},
});

formatMessage(messages.greeting, {name: <b>Eric</b>}); // ['Hello, ', <b>Eric</b>, '!']
```

The message we defined using [`defineMessages`](#definemessages) to support extraction via `babel-plugin-react-intl`, but it doesn't have to be if you're not using the Babel plugin.

**Note:** Messages can be simple strings _without_ placeholders, and that's the most common type of message.
Expand Down
29 changes: 29 additions & 0 deletions docs/Upgrade-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Migrate to using native Intl APIs](#migrate-to-using-native-intl-apis)
- [TypeScript Support](#typescript-support)
- [FormattedRelativeTime](#formattedrelativetime)
- [`formatMessage` now supports `ReactElement`](#formatmessage-now-supports-reactelement)
- [2.0.0](#200)
- [Use React 0.14 or 15](#use-react-014-or-15)
- [Update How Locale Data is Added](#update-how-locale-data-is-added)
Expand Down Expand Up @@ -240,6 +241,34 @@ When we introduced `FormattedRelative`, the spec for [`Intl.RelativeTimeFormat`]

Similarly, the functional counterpart of this component which is `formatRelative` has been renamed to `formatRelativeTime` and its parameters have been changed to reflect this component's props accordingly.

### `formatMessage` now supports `ReactElement`

The imperative API `formatMessage` now supports `ReactElement` in values and will resolve type correctly. This change should be backwards-compatible since for regular non-`ReactElement` values it will still return a `string`, but for rich text like the example down below, it will return a `Array<string, React.ReactElement>`:

```ts
const messages = defineMessages({
greeting: {
id: 'app.greeting',
defaultMessage: 'Hello, {name}!',
description: 'Greeting to welcome the user to the app',
},
});

formatMessage(messages.greeting, {name: 'Eric'}); // "Hello, Eric!"
```

```tsx
const messages = defineMessages({
greeting: {
id: 'app.greeting',
defaultMessage: 'Hello, {name}!',
description: 'Greeting to welcome the user to the app',
},
});

formatMessage(messages.greeting, {name: <b>Eric</b>}); // ['Hello, ', <b>Eric</b>, '!']
```

## 2.0.0

- [Use React 0.14 or 15](#use-react-014-or-15)
Expand Down
126 changes: 63 additions & 63 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,38 +36,38 @@
"react": "global:React"
},
"dependencies": {
"@formatjs/intl-relativetimeformat": "^2.3.4",
"@formatjs/intl-relativetimeformat": "^2.4.0",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/invariant": "^2.2.30",
"@types/react": "^16.8.23",
"hoist-non-react-statics": "^3.3.0",
"intl-format-cache": "^4.0.1",
"intl-locales-supported": "^1.3.4",
"intl-messageformat": "^5.0.1",
"intl-messageformat-parser": "^2.0.1",
"intl-format-cache": "^4.1.0",
"intl-locales-supported": "^1.4.0",
"intl-messageformat": "^5.1.0",
"intl-messageformat-parser": "^2.1.0",
"invariant": "^2.1.1",
"react": "^16.3.0",
"shallow-equal": "^1.1.0"
},
"devDependencies": {
"@babel/cli": "^7.5.0",
"@babel/core": "^7.5.0",
"@babel/core": "^7.5.4",
"@babel/node": "^7.5.0",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/plugin-proposal-object-rest-spread": "^7.5.3",
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
"@babel/plugin-transform-async-to-generator": "^7.5.0",
"@babel/plugin-transform-modules-commonjs": "^7.5.0",
"@babel/preset-env": "^7.5.3",
"@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.0.0",
"@types/benchmark": "^1.0.31",
"@types/enzyme": "^3.10.1",
"@types/enzyme": "^3.10.2",
"@types/jest": "^24.0.13",
"@types/prop-types": "^15.7.1",
"@types/react-dom": "^16.8.4",
"@typescript-eslint/eslint-plugin": "^1.10.2",
"@typescript-eslint/parser": "^1.10.2",
"@typescript-eslint/eslint-plugin": "^1.12.0",
"@typescript-eslint/parser": "^1.12.0",
"babel-jest": "^24.8.0",
"babel-plugin-react-intl": "^4.0.1",
"babel-plugin-react-intl": "^4.1.0",
"babel-plugin-transform-member-expression-literals": "^6.9.4",
"babel-plugin-transform-property-literals": "^6.9.4",
"babel-plugin-transform-react-remove-prop-types": "^0.4.18",
Expand Down
Loading

0 comments on commit 15ed625

Please sign in to comment.