Skip to content

Commit 6e03fa3

Browse files
authored
feat: Use React.Fragment as default textComponent (#1326)
* feat: Use React.Fragment as default textComponent - Fix some `any` casting - Fix tests - Add pre-commit - Update upgrade guide
1 parent 99f2ec0 commit 6e03fa3

File tree

15 files changed

+145
-61
lines changed

15 files changed

+145
-61
lines changed

docs/Upgrade-Guide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
- `addLocaleData` has been removed. See [Migrate to using native Intl APIs](#migrate-to-using-native-intl-apis) for more details.
1515
- `ReactIntlLocaleData` has been removed. See [Migrate to using native Intl APIs](#migrate-to-using-native-intl-apis) for more details.
1616
- `intlShape` has been removed. See [TypeScript Support](#typescript-support) for more details.
17+
- Change default `textComponent` in `IntlProvider` to `React.Fragment`. In order to keep the old behavior, you can explicitly set `textComponent` to `span`.
18+
19+
```tsx
20+
<IntlProvider textComponent="span" />
21+
```
1722

1823
### Use React 16.3 and upwards
1924

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
"intl-pluralrules": "^1.0.1",
112112
"jest": "^24.8.0",
113113
"mkdirp": "^0.5.1",
114+
"pre-commit": "^1.2.2",
114115
"prettier": "^1.6.1",
115116
"progress": "^2.0.3",
116117
"promise-queue": "^2.2.5",
@@ -149,5 +150,9 @@
149150
"examples:link": "yarn link && babel-node scripts/examples yarn link react-intl",
150151
"preversion": "yarn run clean && yarn run build && yarn run test:all",
151152
"prepare": "yarn run clean && yarn run build"
152-
}
153+
},
154+
"pre-commit": [
155+
"format:fix",
156+
"lint:fix"
157+
]
153158
}

src/components/message.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ export class BaseFormattedMessage extends React.Component<Props> {
7474
}
7575

7676
render() {
77-
const {formatMessage = defaultFormatMessage, textComponent: Text = 'span'} =
78-
this.props.intl || {};
77+
const {
78+
formatMessage = defaultFormatMessage,
79+
textComponent: Text = React.Fragment,
80+
} = this.props.intl || {};
7981

8082
const {
8183
id,

src/components/provider.tsx

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66

77
import * as React from 'react';
88
import withIntl, {Provider, WrappedComponentProps} from './injectIntl';
9-
import IntlMessageFormat from 'intl-messageformat';
10-
import IntlRelativeFormat from 'intl-relativeformat';
11-
import memoizeIntlConstructor from 'intl-format-cache';
9+
1210
import * as invariant_ from 'invariant';
1311
// Since rollup cannot deal with namespace being a function,
1412
// this is to interop with TypeScript since `invariant`
1513
// does not export a default
1614
// https://github.com/rollup/rollup/issues/1267
1715
const invariant = invariant_;
18-
import {createError, filterProps, DEFAULT_INTL_CONFIG} from '../utils';
16+
import {
17+
createError,
18+
filterProps,
19+
DEFAULT_INTL_CONFIG,
20+
DEFAULT_FORMATTERS,
21+
} from '../utils';
1922
import {IntlConfig, IntlShape, IntlFormatters} from '../types';
2023
import {formatters} from '../format';
2124
import areIntlLocalesSupported from 'intl-locales-supported';
@@ -34,15 +37,6 @@ const intlConfigPropNames: Array<keyof IntlConfig> = [
3437

3538
'onError',
3639
];
37-
const intlFormatPropNames: Array<keyof IntlFormatters> = [
38-
'formatDate',
39-
'formatTime',
40-
'formatRelative',
41-
'formatNumber',
42-
'formatPlural',
43-
'formatMessage',
44-
'formatHTMLMessage',
45-
];
4640

4741
function getConfig(filteredProps: OptionalIntlConfig): IntlConfig {
4842
let config: IntlConfig = {
@@ -86,20 +80,38 @@ function getConfig(filteredProps: OptionalIntlConfig): IntlConfig {
8680
return config;
8781
}
8882

89-
function getBoundFormatFns(config: IntlConfig, state: State) {
83+
function getBoundFormatFns(config: IntlConfig, state: State): IntlFormatters {
9084
const formatterState = {...state.context.formatters, now: state.context.now};
9185

92-
return intlFormatPropNames.reduce(
93-
(boundFormatFns: IntlFormatters, name) => {
94-
boundFormatFns[name] = (formatters[name] as any).bind(
95-
undefined,
96-
config,
97-
formatterState
98-
);
99-
return boundFormatFns;
100-
},
101-
{} as any
102-
) as IntlFormatters;
86+
return {
87+
formatNumber: formatters.formatNumber.bind(
88+
undefined,
89+
config,
90+
formatterState
91+
),
92+
formatRelative: formatters.formatRelative.bind(
93+
undefined,
94+
config,
95+
formatterState
96+
),
97+
formatDate: formatters.formatDate.bind(undefined, config, formatterState),
98+
formatTime: formatters.formatTime.bind(undefined, config, formatterState),
99+
formatPlural: formatters.formatPlural.bind(
100+
undefined,
101+
config,
102+
formatterState
103+
),
104+
formatMessage: formatters.formatMessage.bind(
105+
undefined,
106+
config,
107+
formatterState
108+
),
109+
formatHTMLMessage: formatters.formatHTMLMessage.bind(
110+
undefined,
111+
config,
112+
formatterState
113+
),
114+
};
103115
}
104116

105117
interface InternalProps {
@@ -149,15 +161,7 @@ class IntlProvider extends React.PureComponent<ResolvedProps, State> {
149161
// `<IntlProvider>`, then its formatters will be used. Otherwise, this
150162
// memoize the `Intl*` constructors and cache them for the lifecycle of
151163
// this IntlProvider instance.
152-
const {
153-
formatters = {
154-
getDateTimeFormat: memoizeIntlConstructor(Intl.DateTimeFormat),
155-
getNumberFormat: memoizeIntlConstructor(Intl.NumberFormat),
156-
getMessageFormat: memoizeIntlConstructor(IntlMessageFormat),
157-
getRelativeFormat: memoizeIntlConstructor(IntlRelativeFormat),
158-
getPluralRules: memoizeIntlConstructor(Intl.PluralRules),
159-
},
160-
} = intlContext || {};
164+
const {formatters = DEFAULT_FORMATTERS} = intlContext || {};
161165

162166
this.state = {
163167
context: {

src/format.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
MessageDescriptor,
2727
} from './types';
2828

29-
import {createError, defaultErrorHandler, escape, filterProps} from './utils';
29+
import {createError, escape, filterProps} from './utils';
3030

3131
const DATE_TIME_FORMAT_OPTIONS: Array<keyof Intl.DateTimeFormatOptions> = [
3232
'localeMatcher',

src/utils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ file in the root directory of React's source tree.
1111

1212
import * as invariant_ from 'invariant';
1313
import {IntlConfig} from './types';
14+
import * as React from 'react';
15+
import IntlMessageFormat from 'intl-messageformat';
16+
import IntlRelativeFormat from 'intl-relativeformat';
17+
import memoizeIntlConstructor from 'intl-format-cache';
1418
// Since rollup cannot deal with namespace being a function,
1519
// this is to interop with TypeScript since `invariant`
1620
// does not export a default
@@ -87,10 +91,18 @@ export const DEFAULT_INTL_CONFIG: Pick<
8791
formats: {},
8892
messages: {},
8993
timeZone: undefined,
90-
textComponent: 'span',
94+
textComponent: React.Fragment,
9195

9296
defaultLocale: 'en',
9397
defaultFormats: {},
9498

9599
onError: defaultErrorHandler,
96100
};
101+
102+
export const DEFAULT_FORMATTERS = {
103+
getDateTimeFormat: memoizeIntlConstructor(Intl.DateTimeFormat),
104+
getNumberFormat: memoizeIntlConstructor(Intl.NumberFormat),
105+
getMessageFormat: memoizeIntlConstructor(IntlMessageFormat),
106+
getRelativeFormat: memoizeIntlConstructor(IntlRelativeFormat),
107+
getPluralRules: memoizeIntlConstructor(Intl.PluralRules),
108+
};

test/functional/support/format.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function(ReactIntl) {
2323
const date = new Date();
2424
const el = <FormattedDate id="test" value={date} month="numeric" />;
2525

26-
const rendered = renderWithIntlProvider(el).find('#test > span');
26+
const rendered = renderWithIntlProvider(el);
2727
expect(rendered.text()).toBe(String(date.getMonth() + 1));
2828
});
2929

@@ -34,7 +34,7 @@ export default function(ReactIntl) {
3434
const hours = date.getHours();
3535
const minutes = date.getMinutes();
3636

37-
const rendered = renderWithIntlProvider(el).find('#test > span');
37+
const rendered = renderWithIntlProvider(el);
3838
expect(rendered.text()).toBe(
3939
`${hours > 12 ? hours % 12 : hours || '12'}:` +
4040
`${minutes < 10 ? `0${minutes}` : minutes} ` +
@@ -48,14 +48,14 @@ export default function(ReactIntl) {
4848
<FormattedRelative id="test" value={now - 1000} initialNow={now} />
4949
);
5050

51-
const rendered = renderWithIntlProvider(el).find('#test > span');
51+
const rendered = renderWithIntlProvider(el);
5252
expect(rendered.text()).toBe('1 second ago');
5353
});
5454

5555
it('formats numbers with thousands separators', () => {
5656
const el = <FormattedNumber id="test" value={1000} />;
5757

58-
const rendered = renderWithIntlProvider(el).find('#test > span');
58+
const rendered = renderWithIntlProvider(el);
5959
expect(rendered.text()).toBe('1,000');
6060
});
6161

@@ -64,7 +64,7 @@ export default function(ReactIntl) {
6464
<FormattedNumber id="test" value={0.1} minimumFractionDigits={2} />
6565
);
6666

67-
const rendered = renderWithIntlProvider(el).find('#test > span');
67+
const rendered = renderWithIntlProvider(el);
6868
expect(rendered.text()).toBe('0.10');
6969
});
7070

@@ -79,7 +79,7 @@ export default function(ReactIntl) {
7979
/>
8080
);
8181

82-
const rendered = renderWithIntlProvider(el).find('#test > span');
82+
const rendered = renderWithIntlProvider(el);
8383
expect(rendered.text()).toBe('You have 1,000 emails.');
8484
});
8585
});

test/unit/components/date.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('<FormattedDate>', () => {
5454

5555
const rendered = shallowDeep(<FormattedDate value={date} />, 2);
5656

57-
expect(rendered.type()).toBe('span');
57+
expect(typeof rendered.type()).toBe('symbol');
5858
expect(rendered.text()).toBe(intl.formatDate(date));
5959
});
6060

test/unit/components/html-message.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('<FormattedHTMLMessage>', () => {
4242

4343
const rendered = shallowDeep(<FormattedHTMLMessage {...descriptor} />, 2);
4444

45-
expect(rendered.type()).toBe('span');
45+
expect(typeof rendered.type()).toBe('symbol');
4646
expect(rendered.prop('dangerouslySetInnerHTML')).toEqual({
4747
__html: intl.formatHTMLMessage(descriptor),
4848
});

test/unit/components/message.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,21 @@ describe('<FormattedMessage>', () => {
4343
2
4444
);
4545

46-
expect(rendered.type()).toBe('span');
46+
expect(typeof rendered.type()).toBe('symbol');
4747
expect(rendered.text()).toBe('Hello');
4848

4949
expect(consoleError).toHaveBeenCalledTimes(1);
5050
});
5151

52-
it('renders a formatted message in a <span>', () => {
52+
it('renders a formatted message in a <>', () => {
5353
const FormattedMessage = mockContext(intl);
5454
const descriptor = {
5555
id: 'hello',
5656
defaultMessage: 'Hello, World!',
5757
};
5858

5959
const rendered = shallowDeep(<FormattedMessage {...descriptor} />, 2);
60-
expect(rendered.type()).toBe('span');
60+
expect(typeof rendered.type()).toBe('symbol');
6161
expect(rendered.text()).toBe(intl.formatMessage(descriptor));
6262
});
6363

@@ -122,7 +122,7 @@ describe('<FormattedMessage>', () => {
122122
2
123123
);
124124

125-
expect(rendered.type()).toBe('span');
125+
expect(typeof rendered.type()).toBe('symbol');
126126
expect(rendered.text()).toBe(intl.formatMessage(descriptor, values));
127127
});
128128

0 commit comments

Comments
 (0)