Skip to content

Commit f2d6439

Browse files
perf(7292): Remove LegacyI18nProvider (legacy context bridge) and LegacyMetaMetricsProvider (#43483)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** React 18 emits `childContextTypes` deprecation warnings for `LegacyI18nProvider` in `ui/contexts/i18n.js`. This class component bridges `I18nContext` (modern `createContext`) into the old `childContextTypes` API so legacy class components can access `this.context.t`. Warning appears in ~795 test files. Also the same pattern, `LegacyMetaMetricsProvider` in `ui/contexts/metametrics.tsx` bridges `MetaMetricsContext `into legacy `childContextTypes`. Warning appears in ~782 test files. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: MetaMask/MetaMask-planning#7292 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches many UI surfaces (connect, permissions, home, unlock, transactions) for i18n and analytics wiring; behavior should be equivalent but regressions are possible in untested class-component paths. > > **Overview** > Removes **`LegacyI18nProvider`** and **`LegacyMetaMetricsProvider`**, which bridged modern `createContext` into deprecated `childContextTypes` / `getChildContext` (React 18 warnings in large test suites). > > **i18n:** Class components now use `static contextType = I18nContext` and treat context as the `t` function directly (`const t = this.context` / `this.context('key')`) instead of `this.context.t`. **`I18nProvider`** in `ui/contexts/i18n.js` is simplified (no legacy wrapper). > > **MetaMetrics:** Components that used legacy `this.context.trackEvent` get **`trackEvent` via props** from thin functional wrappers using `useContext(MetaMetricsContext)` (e.g. `PermissionPageContainer`, `Home`, `UnlockPage`, `ConfirmEncryptionPublicKey`, `TransactionListItemDetails` container). **`UnlockPage`** drops `withMetaMetrics` in favor of that wrapper pattern. > > **App shell & tests:** Storybook, `ui/pages/index.js`, and render helpers no longer nest legacy providers; tests wrap with `I18nContext` / `MetaMetricsContext` only where needed. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5a70d5b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ccf69a9 commit f2d6439

32 files changed

Lines changed: 210 additions & 347 deletions

File tree

.storybook/i18n.js

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Component, useMemo } from 'react';
1+
import React, { useMemo } from 'react';
22
import PropTypes from 'prop-types';
33
import { getMessage } from '../ui/helpers/utils/i18n-helper';
44
import { I18nContext } from '../ui/contexts/i18n';
@@ -29,29 +29,3 @@ I18nProvider.propTypes = {
2929
I18nProvider.defaultProps = {
3030
children: undefined,
3131
};
32-
33-
export class LegacyI18nProvider extends Component {
34-
static propTypes = {
35-
children: PropTypes.node,
36-
};
37-
38-
static defaultProps = {
39-
children: undefined,
40-
};
41-
42-
static contextType = I18nContext;
43-
44-
static childContextTypes = {
45-
t: PropTypes.func,
46-
};
47-
48-
getChildContext() {
49-
return {
50-
t: this.context,
51-
};
52-
}
53-
54-
render() {
55-
return this.props.children;
56-
}
57-
}

.storybook/metametrics.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import React from 'react';
2-
import {
3-
MetaMetricsProvider,
4-
LegacyMetaMetricsProvider
5-
} from '../ui/contexts/metametrics';
2+
import { MetaMetricsProvider } from '../ui/contexts/metametrics';
63

7-
const MetaMetricsProviderStorybook = (props) =>
8-
(
9-
<MetaMetricsProvider>
10-
<LegacyMetaMetricsProvider>
11-
{props.children}
12-
</LegacyMetaMetricsProvider>
13-
</MetaMetricsProvider>
14-
);
4+
const MetaMetricsProviderStorybook = (props) => (
5+
<MetaMetricsProvider>{props.children}</MetaMetricsProvider>
6+
);
157

16-
export default MetaMetricsProviderStorybook
8+
export default MetaMetricsProviderStorybook;

.storybook/preview.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import configureStore from '../ui/store/store';
99
import '../ui/css/index.scss';
1010
import localeList from '../app/_locales/index.json';
1111
import * as allLocales from './locales';
12-
import { I18nProvider, LegacyI18nProvider } from './i18n';
12+
import { I18nProvider } from './i18n';
1313
import testData from './test-data.js';
1414
import { MemoryRouter, Routes, Route } from 'react-router-dom';
1515
import { setBackgroundConnection } from '../ui/store/background-connection';
@@ -142,11 +142,9 @@ const metamaskDecorator = (story, context) => {
142142
current={current}
143143
en={allLocales.en}
144144
>
145-
<LegacyI18nProvider>
146-
<Routes>
147-
<Route path={path} element={<StoryComponent />} />
148-
</Routes>
149-
</LegacyI18nProvider>
145+
<Routes>
146+
<Route path={path} element={<StoryComponent />} />
147+
</Routes>
150148
</I18nProvider>
151149
</AlertMetricsProvider>
152150
</MemoryRouter>

test/jest/console-baseline-integration.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
"warn: ⚠️ React Router Future Flag": 1
134134
},
135135
"test/integration/notifications&auth/notifications-activation.test.tsx": {
136+
"React: State updates on unmounted components": 1,
136137
"Reselect: Input stability warnings": 6,
137138
"warn: Selector unknown returned a different": 2,
138139
"warn: ⚠️ React Router Future Flag": 1

test/lib/render-helpers-navigate.js

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
99
import PropTypes from 'prop-types';
1010
import { noop } from 'lodash';
1111
import configureStore from '../../ui/store/store';
12-
import { I18nContext, LegacyI18nProvider } from '../../ui/contexts/i18n';
13-
import {
14-
MetaMetricsContext,
15-
LegacyMetaMetricsProvider,
16-
} from '../../ui/contexts/metametrics';
12+
import { I18nContext } from '../../ui/contexts/i18n';
13+
import { MetaMetricsContext } from '../../ui/contexts/metametrics';
1714
import { getMessage } from '../../ui/helpers/utils/i18n-helper';
1815
import * as enLocaleMessages from '../../app/_locales/en/messages.json';
1916
import {
@@ -140,17 +137,13 @@ export function createProviderWrapper(
140137
return (
141138
<MemoryRouter>
142139
<I18nProvider currentLocale="en" current={en} en={en}>
143-
<LegacyI18nProvider>
144-
<UIMessengerProvider value={uiMessenger}>
145-
<MetaMetricsContext.Provider value={mockMetaMetricsContext}>
146-
<LegacyMetaMetricsProvider>
147-
<QueryClientProvider client={queryClient}>
148-
{content}
149-
</QueryClientProvider>
150-
</LegacyMetaMetricsProvider>
151-
</MetaMetricsContext.Provider>
152-
</UIMessengerProvider>
153-
</LegacyI18nProvider>
140+
<UIMessengerProvider value={uiMessenger}>
141+
<MetaMetricsContext.Provider value={mockMetaMetricsContext}>
142+
<QueryClientProvider client={queryClient}>
143+
{content}
144+
</QueryClientProvider>
145+
</MetaMetricsContext.Provider>
146+
</UIMessengerProvider>
154147
</I18nProvider>
155148
</MemoryRouter>
156149
);
@@ -255,7 +248,7 @@ export const renderHookWithProviderTyped = (
255248
export function renderWithLocalization(component) {
256249
const Wrapper = ({ children }) => (
257250
<I18nProvider currentLocale="en" current={en} en={en}>
258-
<LegacyI18nProvider>{children}</LegacyI18nProvider>
251+
{children}
259252
</I18nProvider>
260253
);
261254

test/lib/render-helpers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render } from '@testing-library/react';
33
import { userEvent } from '@testing-library/user-event';
44
import PropTypes from 'prop-types';
55
import { noop } from 'lodash';
6-
import { I18nContext, LegacyI18nProvider } from '../../ui/contexts/i18n';
6+
import { I18nContext } from '../../ui/contexts/i18n';
77
import { getMessage } from '../../ui/helpers/utils/i18n-helper';
88
import * as en from '../../app/_locales/en/messages.json';
99
import { setupInitialStore, connectToBackground } from '../../ui';
@@ -38,7 +38,7 @@ I18nProvider.defaultProps = {
3838
export function renderWithLocalization(component) {
3939
const Wrapper = ({ children }) => (
4040
<I18nProvider currentLocale="en" current={en} en={en}>
41-
<LegacyI18nProvider>{children}</LegacyI18nProvider>
41+
{children}
4242
</I18nProvider>
4343
);
4444

ui/components/app/connected-accounts-list/connected-accounts-list.component.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ import {
1111
Text,
1212
} from '../../component-library';
1313
import { MenuItem } from '../../ui/menu';
14+
import { I18nContext } from '../../../contexts/i18n';
1415
import ConnectedAccountsListItem from './connected-accounts-list-item';
1516
import ConnectedAccountsListOptions from './connected-accounts-list-options';
1617

1718
export default class ConnectedAccountsList extends PureComponent {
18-
static contextTypes = {
19-
t: PropTypes.func.isRequired,
20-
};
19+
static contextType = I18nContext;
2120

2221
static defaultProps = {
2322
accountToConnect: null,
@@ -87,7 +86,7 @@ export default class ConnectedAccountsList extends PureComponent {
8786

8887
renderUnconnectedAccount() {
8988
const { accountToConnect, connectAccount } = this.props;
90-
const { t } = this.context;
89+
const t = this.context;
9190

9291
if (!accountToConnect) {
9392
return null;
@@ -121,7 +120,7 @@ export default class ConnectedAccountsList extends PureComponent {
121120

122121
renderListItemOptions(address) {
123122
const { accountWithOptionsShown } = this.state;
124-
const { t } = this.context;
123+
const t = this.context;
125124

126125
return (
127126
<ConnectedAccountsListOptions
@@ -140,7 +139,7 @@ export default class ConnectedAccountsList extends PureComponent {
140139
}
141140

142141
renderListItemAction(address) {
143-
const { t } = this.context;
142+
const t = this.context;
144143

145144
return (
146145
<Text variant={TextVariant.bodyMd}>
@@ -158,7 +157,7 @@ export default class ConnectedAccountsList extends PureComponent {
158157
render() {
159158
const { connectedAccounts, selectedAddress, shouldRenderListOptions } =
160159
this.props;
161-
const { t } = this.context;
160+
const t = this.context;
162161

163162
return (
164163
<>

ui/components/app/connected-sites-list/connected-sites-list.component.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import { stripHttpsSchemeWithoutPort } from '../../../helpers/utils/util';
77
import SiteOrigin from '../../ui/site-origin';
88
import { Size } from '../../../helpers/constants/design-system';
99
import { SnapIcon } from '../snaps/snap-icon';
10+
import { I18nContext } from '../../../contexts/i18n';
1011

1112
export default class ConnectedSitesList extends Component {
12-
static contextTypes = {
13-
t: PropTypes.func,
14-
};
13+
static contextType = I18nContext;
1514

1615
static propTypes = {
1716
connectedSubjects: PropTypes.arrayOf(
@@ -27,7 +26,7 @@ export default class ConnectedSitesList extends Component {
2726

2827
getConnectedSitesListContent = () => {
2928
const { connectedSubjects, onDisconnect, getSnapName } = this.props;
30-
const { t } = this.context;
29+
const t = this.context;
3130
return connectedSubjects.map((subject) => {
3231
if (isSnapId(subject.origin)) {
3332
const snapName = getSnapName(subject.origin);
@@ -91,7 +90,7 @@ export default class ConnectedSitesList extends Component {
9190

9291
getSubjectDisplayName(subject) {
9392
if (subject.extensionId) {
94-
return this.context.t('externalExtension');
93+
return this.context('externalExtension');
9594
}
9695

9796
// We strip https schemes only, and only if the URL has no port.

ui/components/app/import-token/token-list/token-list.component.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../../shared/constan
2727
import TokenListPlaceholder from './token-list-placeholder';
2828

2929
export default class TokenList extends Component {
30-
static contextTypes = {
31-
t: PropTypes.func,
32-
};
33-
3430
static propTypes = {
3531
allTokens: PropTypes.object,
3632
results: PropTypes.array,

ui/components/app/modals/confirm-delete-network/confirm-delete-network.component.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { PureComponent } from 'react';
22
import PropTypes from 'prop-types';
33
import { toEvmCaipChainId } from '@metamask/multichain-network-controller';
4+
import { I18nContext } from '../../../../contexts/i18n';
45

56
import Modal, { ModalContent } from '../../modal';
67

@@ -15,9 +16,7 @@ export default class ConfirmDeleteNetwork extends PureComponent {
1516
switchToEthereumNetwork: PropTypes.func,
1617
};
1718

18-
static contextTypes = {
19-
t: PropTypes.func,
20-
};
19+
static contextType = I18nContext;
2120

2221
handleDelete = async () => {
2322
const {
@@ -44,7 +43,7 @@ export default class ConfirmDeleteNetwork extends PureComponent {
4443
};
4544

4645
render() {
47-
const { t } = this.context;
46+
const t = this.context;
4847
const { networkNickname } = this.props;
4948

5049
return (

0 commit comments

Comments
 (0)