Skip to content

Commit

Permalink
Merge pull request #4253 from kidroca/kidroca/locale-perf-fix
Browse files Browse the repository at this point in the history
Perf: remove Onyx usage from `withLocalize`
  • Loading branch information
marcaaron authored Jul 30, 2021
2 parents 38b6791 + 72800fc commit 8a958f2
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 116 deletions.
11 changes: 7 additions & 4 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {SafeAreaProvider} from 'react-native-safe-area-context';
import CustomStatusBar from './components/CustomStatusBar';
import ErrorBoundary from './components/ErrorBoundary';
import Expensify from './Expensify';
import {LocaleContextProvider} from './components/withLocalize';

LogBox.ignoreLogs([
// Basically it means that if the app goes in the background and back to foreground on Android,
Expand All @@ -17,10 +18,12 @@ LogBox.ignoreLogs([

const App = () => (
<SafeAreaProvider>
<CustomStatusBar />
<ErrorBoundary errorMessage="E.cash crash caught by error boundary">
<Expensify />
</ErrorBoundary>
<LocaleContextProvider>
<CustomStatusBar />
<ErrorBoundary errorMessage="E.cash crash caught by error boundary">
<Expensify />
</ErrorBoundary>
</LocaleContextProvider>
</SafeAreaProvider>
);

Expand Down
224 changes: 112 additions & 112 deletions src/components/withLocalize.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {createContext, forwardRef} from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import getComponentDisplayName from '../libs/getComponentDisplayName';
Expand All @@ -9,6 +9,8 @@ import {toLocalPhone, fromLocalPhone} from '../libs/LocalePhoneNumber';
import numberFormat from '../libs/numberFormat';
import CONST from '../CONST';

const LocaleContext = createContext(null);

const withLocalizePropTypes = {
/** Returns translated string for given locale and phrase */
translate: PropTypes.func.isRequired,
Expand All @@ -29,124 +31,122 @@ const withLocalizePropTypes = {
fromLocalPhone: PropTypes.func.isRequired,
};

export default (WrappedComponent) => {
const propTypes = {
/** The user's preferred locale e.g. 'en', 'es-ES' */
preferredLocale: PropTypes.string,

/** Passed ref from whatever component is wrapped in the HOC */
forwardedRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({current: PropTypes.instanceOf(React.Component)}),
]),
};

const defaultProps = {
preferredLocale: CONST.DEFAULT_LOCALE,
forwardedRef: undefined,
};

class WithLocalize extends React.Component {
constructor(props) {
super(props);

this.translate = this.translate.bind(this);
this.numberFormat = this.numberFormat.bind(this);
this.timestampToRelative = this.timestampToRelative.bind(this);
this.timestampToDateTime = this.timestampToDateTime.bind(this);
this.fromLocalPhone = this.fromLocalPhone.bind(this);
this.toLocalPhone = this.toLocalPhone.bind(this);
}

/**
* @param {String} phrase
* @param {Object} [variables]
* @returns {String}
*/
translate(phrase, variables) {
return translate(this.props.preferredLocale, phrase, variables);
}

/**
* @param {Number} number
* @param {Intl.NumberFormatOptions} options
* @returns {String}
*/
numberFormat(number, options) {
return numberFormat(this.props.preferredLocale, number, options);
}

/**
* @param {Number} timestamp
* @returns {String}
*/
timestampToRelative(timestamp) {
return DateUtils.timestampToRelative(this.props.preferredLocale, timestamp);
}

/**
* @param {Number} timestamp
* @param {Boolean} [includeTimezone]
* @returns {String}
*/
timestampToDateTime(timestamp, includeTimezone) {
return DateUtils.timestampToDateTime(
this.props.preferredLocale,
timestamp,
includeTimezone,
);
}

/**
* @param {Number} number
* @returns {String}
*/
toLocalPhone(number) {
return toLocalPhone(this.props.preferredLocale, number);
}

/**
* @param {Number} number
* @returns {String}
*/
fromLocalPhone(number) {
return fromLocalPhone(this.props.preferredLocale, number);
}

render() {
return (
<WrappedComponent
// eslint-disable-next-line react/jsx-props-no-spreading
{...this.props}
ref={this.props.forwardedRef}
translate={this.translate}
numberFormat={this.numberFormat}
timestampToRelative={this.timestampToRelative}
timestampToDateTime={this.timestampToDateTime}
toLocalPhone={this.toLocalPhone}
fromLocalPhone={this.fromLocalPhone}
/>
);
}
const localeProviderPropTypes = {
/** The user's preferred locale e.g. 'en', 'es-ES' */
preferredLocale: PropTypes.string,

/* Actual content wrapped by this component */
children: PropTypes.node.isRequired,
};

const localeProviderDefaultProps = {
preferredLocale: CONST.DEFAULT_LOCALE,
};

class LocaleContextProvider extends React.Component {
constructor(props) {
super(props);

/* The context this component exposes to consumers */
this.translateUtils = {
translate: this.translate.bind(this),
numberFormat: this.numberFormat.bind(this),
timestampToRelative: this.timestampToRelative.bind(this),
timestampToDateTime: this.timestampToDateTime.bind(this),
fromLocalPhone: this.fromLocalPhone.bind(this),
toLocalPhone: this.toLocalPhone.bind(this),
};
}

WithLocalize.propTypes = propTypes;
WithLocalize.defaultProps = defaultProps;
/**
* @param {String} phrase
* @param {Object} [variables]
* @returns {String}
*/
translate(phrase, variables) {
return translate(this.props.preferredLocale, phrase, variables);
}

/**
* @param {Number} number
* @param {Intl.NumberFormatOptions} options
* @returns {String}
*/
numberFormat(number, options) {
return numberFormat(this.props.preferredLocale, number, options);
}

const withForwardedRef = React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<WithLocalize {...props} forwardedRef={ref} />
/**
* @param {Number} timestamp
* @returns {String}
*/
timestampToRelative(timestamp) {
return DateUtils.timestampToRelative(this.props.preferredLocale, timestamp);
}

/**
* @param {Number} timestamp
* @param {Boolean} [includeTimezone]
* @returns {String}
*/
timestampToDateTime(timestamp, includeTimezone) {
return DateUtils.timestampToDateTime(
this.props.preferredLocale,
timestamp,
includeTimezone,
);
}

/**
* @param {Number} number
* @returns {String}
*/
toLocalPhone(number) {
return toLocalPhone(this.props.preferredLocale, number);
}

/**
* @param {Number} number
* @returns {String}
*/
fromLocalPhone(number) {
return fromLocalPhone(this.props.preferredLocale, number);
}

render() {
return (
<LocaleContext.Provider value={this.translateUtils}>
{this.props.children}
</LocaleContext.Provider>
);
}
}

LocaleContextProvider.propTypes = localeProviderPropTypes;
LocaleContextProvider.defaultProps = localeProviderDefaultProps;

const Provider = withOnyx({
preferredLocale: {
key: ONYXKEYS.NVP_PREFERRED_LOCALE,
},
})(LocaleContextProvider);

Provider.displayName = 'withOnyx(LocaleContextProvider)';

export default function withLocalize(WrappedComponent) {
const WithLocalize = forwardRef((props, ref) => (
<LocaleContext.Consumer>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
{ translateUtils => <WrappedComponent {...translateUtils} {...props} ref={ref} />}
</LocaleContext.Consumer>
));

withForwardedRef.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`;
WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`;

return withOnyx({
preferredLocale: {
key: ONYXKEYS.NVP_PREFERRED_LOCALE,
},
})(withForwardedRef);
};
return WithLocalize;
}

export {
withLocalizePropTypes,
Provider as LocaleContextProvider,
};

0 comments on commit 8a958f2

Please sign in to comment.