Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(27255): allow local modification for remote feature flags #29696

Merged
merged 55 commits into from
Feb 21, 2025
Merged
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d4f2f3f
feat(27255): allow local modification for remote feature flags
DDDDDanica Jan 14, 2025
887ed35
Update LavaMoat policies
metamaskbot Jan 14, 2025
4cef487
feat(27255): move the file to git ignore and add instructions to Read…
DDDDDanica Jan 16, 2025
5e3788b
feat(29629): Add _flags to webpack build only when in development; an…
DDDDDanica Jan 16, 2025
5c703f2
feat(29629): adapted to right pattern in transform function
DDDDDanica Jan 16, 2025
35fe1f0
feat(29629): relocate `getManifestFlags` to `shared/lib/manifestFlags`
DDDDDanica Jan 16, 2025
473a3bd
feat(29629): enrich `getRemoteFeatureFlags` return type
DDDDDanica Jan 16, 2025
caa4b4f
feat(29629): shallow merge states in `getRemoteFeatureFlags`
DDDDDanica Jan 16, 2025
0bff314
Update LavaMoat policies
metamaskbot Jan 16, 2025
d4e79a7
feat(29629): fix e2e test due to shallow merging
DDDDDanica Jan 17, 2025
8e88e7f
feat(29629): Update readme
DDDDDanica Jan 17, 2025
b1a52ae
feat(29629): Remove unused [] param in getWebpackConfig test
DDDDDanica Jan 17, 2025
b2b5862
feat(29629): Remove unnecessary --test flag in non-default options te…
DDDDDanica Jan 17, 2025
a5ea54f
feat(29629): Remove comment in json file to avoid copy error; read .m…
DDDDDanica Jan 17, 2025
5308bed
feat(29629): Move getRemoteFeatureFlags selector to a separate ts file
DDDDDanica Jan 19, 2025
2cc1560
feat(29629): Rename remote-feature-flags.ts file name
DDDDDanica Jan 19, 2025
248ec01
feat(29629): Refactor getRemoteFeatureFlags to use safe merge from lo…
DDDDDanica Jan 19, 2025
40f3c07
feat(29629): Rename gitnore comment
DDDDDanica Jan 19, 2025
3a16019
feat(29629): Remove asyn reading for loadManifestFlags in normal build
DDDDDanica Jan 19, 2025
77ad1e5
feat(29629): Fix unit test of file not exist
DDDDDanica Jan 19, 2025
b09f6a5
feat(29629): Fix lint
DDDDDanica Jan 20, 2025
9487c17
Merge branch 'main' into feature/remote-feature-flags-manifest-adaption
DDDDDanica Jan 26, 2025
51b6de6
fix(3742): fix lint
DDDDDanica Jan 26, 2025
f3c8e38
feat(27255): import json file from `.metamaskrc` instead of using rea…
DDDDDanica Jan 31, 2025
2c5ab32
feat(27255): fix lint
DDDDDanica Jan 31, 2025
945c42a
feat(27255): removed console log in build console
DDDDDanica Jan 31, 2025
f170665
Merge branch 'main' into feature/remote-feature-flags-manifest-adaption
DDDDDanica Feb 3, 2025
8be5eb3
feat(27255): update README.md
DDDDDanica Feb 3, 2025
f2cee6a
feat(27255): add the * wildcard to ignored filed name
DDDDDanica Feb 3, 2025
3a53d83
feat(27255): fix lint and try to fix "TypeError: Cannot destructure p…
DDDDDanica Feb 3, 2025
a721328
Merge branch 'main' into feature/remote-feature-flags-manifest-adaption
DDDDDanica Feb 11, 2025
ff643b6
Simplify remoteFeatureFlag type in manifestFlags.ts file
DDDDDanica Feb 11, 2025
af74af6
Modify plugins.ManifestPlugin.test.ts to use mock utility from node:test
DDDDDanica Feb 11, 2025
7c3ff23
Add silently ignores non-ENOENT filesystem unit test
DDDDDanica Feb 11, 2025
ab7448d
Make manifest.overrides.json more general
DDDDDanica Feb 12, 2025
581831c
Fix lint; revert to `fs.readFile`
DDDDDanica Feb 12, 2025
b120921
Merge branch 'main' into feature/remote-feature-flags-manifest-adaption
HowardBraham Feb 12, 2025
e950aa3
Fix failed unit tests
DDDDDanica Feb 13, 2025
b801c6c
Merge branch 'main' into feature/remote-feature-flags-manifest-adaption
DDDDDanica Feb 13, 2025
84bc8f1
Add more unit tests for overwrite manifest case
DDDDDanica Feb 13, 2025
86211f3
Merge remote-tracking branch 'origin/feature/remote-feature-flags-man…
DDDDDanica Feb 13, 2025
b768704
Changed copy of develop options from designer
DDDDDanica Feb 19, 2025
1003d35
Merge branch 'main' into feature/remote-feature-flags-manifest-adaption
DDDDDanica Feb 19, 2025
4248edf
Update snapshot
DDDDDanica Feb 19, 2025
a199f6f
refactor to prevent manifest transforms from modifying original object
DDDDDanica Feb 19, 2025
6e112e8
fix lint for return in the function
DDDDDanica Feb 19, 2025
1071e2b
change develop options context
DDDDDanica Feb 20, 2025
dcb78e3
fix snpapshot
DDDDDanica Feb 20, 2025
96fe46d
Fix lint
DDDDDanica Feb 20, 2025
9f92cc9
Improve word and lint fix for developer options
DDDDDanica Feb 20, 2025
49542c0
Rollback manifestClone copy method
DDDDDanica Feb 20, 2025
0380cf0
Revert addTabsPermission
DDDDDanica Feb 20, 2025
d8fb679
Restore mock after each test
DDDDDanica Feb 20, 2025
5ad5fdb
Update snapshot
DDDDDanica Feb 20, 2025
1e89e33
Update readme
DDDDDanica Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -52,6 +52,9 @@ notes.txt
.metamaskrc
.metamaskprodrc

# Customized manifest configuration
.manifest-overrides*.json

# Test results
test-results/

10 changes: 10 additions & 0 deletions .metamaskrc.dist
Original file line number Diff line number Diff line change
@@ -50,3 +50,13 @@ BLOCKAID_PUBLIC_KEY=
; API key used in Etherscan requests to prevent rate limiting.
; Only applies to Mainnet and Sepolia.
; ETHERSCAN_API_KEY=

; A JSON config file that can be used to override the default manifest values.
; e.g., `.manifest-overrides.json` where the contents might be something like:
; {
; "_flags": {
; "remoteFeatureFlags": { }
; }
; }
; Note: Properties are shallow merged into the manifest.json file at build time.
;MANIFEST_OVERRIDES=.manifest-overrides.json
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -56,6 +56,18 @@ If you are not a MetaMask Internal Developer, or are otherwise developing on a f
- If debugging MetaMetrics, you'll need to add a value for `SEGMENT_WRITE_KEY` [Segment write key](https://segment.com/docs/connections/find-writekey/), see [Developing on MetaMask - Segment](./development/README.md#segment).
- If debugging unhandled exceptions, you'll need to add a value for `SENTRY_DSN` [Sentry Dsn](https://docs.sentry.io/product/sentry-basics/dsn-explainer/), see [Developing on MetaMask - Sentry](./development/README.md#sentry).
- Optionally, replace the `PASSWORD` value with your development wallet password to avoid entering it each time you open the app.
- If developing with remote feature flags, and you want to override the flags in the build process, you can add a `.manifest-overrides.json` file to the root of the project and set `MANIFEST_OVERRIDES=.manifest-overrides.json` in `.metamaskrc` to the path of the file.
This file is used to add flags to `manifest.json` build files for the extension. You can also modify the `_flags.remoteFeatureFlags` in the built version of `manifest.json` in the `dist/browser` folder to tweak the flags after the build process (these changes will get overwritten when you build again).
An example of this remote feature flag overwrite could be:

```json
{
"_flags": {
"remoteFeatureFlags": { "testBooleanFlag": false }
}
}
```

- Run `yarn install` to install the dependencies.
- Build the project to the `./dist/` folder with `yarn dist` (for Chromium-based browsers) or `yarn dist:mv2` (for Firefox)

2 changes: 1 addition & 1 deletion app/scripts/lib/setupSentry.js
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ import * as Sentry from '@sentry/browser';
import { logger } from '@sentry/utils';
import browser from 'webextension-polyfill';
import { isManifestV3 } from '../../../shared/modules/mv3.utils';
import { getManifestFlags } from '../../../shared/lib/manifestFlags';
import extractEthjsErrorMessage from './extractEthjsErrorMessage';
import { getManifestFlags } from './manifestFlags';
import { filterEvents } from './sentry-filter-events';

const projectLogger = createProjectLogger('sentry');
2 changes: 2 additions & 0 deletions builds.yml
Original file line number Diff line number Diff line change
@@ -303,6 +303,8 @@ env:

# Uses yaml anchors to DRY - https://juju.is/docs/sdk/yaml-anchors-and-aliases
- METAMASK_BUILD_TYPE_DEFAULT: *default
# Path to a JSON file that will be used to override the default manifest values.
- MANIFEST_OVERRIDES: null

###
# Account Abstraction (EIP-4337)
1 change: 1 addition & 0 deletions development/build/config.js
Original file line number Diff line number Diff line change
@@ -160,4 +160,5 @@ async function getConfig(buildType, environment) {

module.exports = {
getConfig,
fromIniFile,
};
32 changes: 31 additions & 1 deletion development/build/manifest.js
Original file line number Diff line number Diff line change
@@ -15,9 +15,35 @@ const { loadBuildTypesConfig } = require('../lib/build-type');
const { TASKS, ENVIRONMENT } = require('./constants');
const { createTask, composeSeries } = require('./task');
const { getEnvironment, getBuildName } = require('./utils');
const { fromIniFile } = require('./config');

module.exports = createManifestTasks;

async function loadManifestFlags() {
const { definitions } = await fromIniFile(
path.resolve(__dirname, '..', '..', '.metamaskrc'),
);
const manifestOverridesPath = definitions.get('MANIFEST_OVERRIDES');
// default to undefined so that the manifest plugin can check if it was set
let manifestFlags;
if (manifestOverridesPath) {
try {
manifestFlags = await readJson(
path.resolve(process.cwd(), manifestOverridesPath),
);
} catch (error) {
// Only throw if error is not ENOENT (file not found) and manifestOverridesPath was provided
if (error.code === 'ENOENT') {
throw new Error(
`Manifest override file not found: ${manifestOverridesPath}`,
);
}
}
}

return manifestFlags;
}

function createManifestTasks({
browserPlatforms,
browserVersionMap,
@@ -28,6 +54,9 @@ function createManifestTasks({
}) {
// merge base manifest with per-platform manifests
const prepPlatforms = async () => {
const isDevelopment =
getEnvironment({ buildTarget: entryTask }) === 'development';
const manifestFlags = isDevelopment ? await loadManifestFlags() : undefined;
return Promise.all(
browserPlatforms.map(async (platform) => {
const platformModifications = await readJson(
@@ -47,8 +76,9 @@ function createManifestTasks({
browserVersionMap[platform],
await getBuildModifications(buildType, platform),
customArrayMerge,
// Only include _flags if manifestFlags has content
manifestFlags,
);

modifyNameAndDescForNonProd(result);

const dir = path.join('.', 'dist', platform);
2 changes: 1 addition & 1 deletion development/lib/get-manifest-flag.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import { exec as callbackExec } from 'node:child_process';
import { hasProperty } from '@metamask/utils';
import { merge } from 'lodash';

import type { ManifestFlags } from '../../app/scripts/lib/manifestFlags';
import type { ManifestFlags } from '../../shared/lib/manifestFlags';

const exec = promisify(callbackExec);
const PR_BODY_FILEPATH = path.resolve(
118 changes: 116 additions & 2 deletions development/webpack/test/plugins.ManifestPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it } from 'node:test';
import { describe, it, afterEach } from 'node:test';
import assert from 'node:assert';
import { join } from 'node:path';
import { type Compilation } from 'webpack';
@@ -232,7 +232,7 @@ describe('ManifestPlugin', () => {
function runTest(baseManifest: Combination<typeof manifestMatrix>) {
const manifest = baseManifest as unknown as chrome.runtime.Manifest;
const hasTabsPermission = (manifest.permissions || []).includes('tabs');
const transform = transformManifest(args);
const transform = transformManifest(args, false);

if (args.test && hasTabsPermission) {
it("throws in test mode when manifest already contains 'tabs' permission", () => {
@@ -281,4 +281,118 @@ describe('ManifestPlugin', () => {
}
}
});

describe('manifest flags in development mode', () => {
const emptyTestManifest = {} as chrome.runtime.Manifest;
const notEmptyTestManifest = {
_flags: { remoteFeatureFlags: { testFlag: false, testFlag2: 'value1' } },
} as unknown as chrome.runtime.Manifest;
const mockFlags = { _flags: { remoteFeatureFlags: { testFlag: true } } };
const manifestOverridesPath = 'testManifestOverridesPath.json';
const fs = require('node:fs');
const { mock } = require('node:test');
const { resolve } = require('node:path');

afterEach(() => mock.restoreAll());

it('adds manifest flags in development mode with path provided and empty manifest', () => {
mock.method(fs, 'readFileSync', (path: string, options: object) => {
if (path === resolve(__dirname, '../../../', manifestOverridesPath)) {
return JSON.stringify(mockFlags);
}
return fs.readFileSync.original(path, options);
});
const transform = transformManifest(
{ lockdown: true, test: false },
true,
manifestOverridesPath,
);
assert(transform, 'transform should be truthy');

const transformed = transform(emptyTestManifest, 'chrome');
console.log('Transformed:', transformed);
assert.deepStrictEqual(
transformed,
mockFlags,
'manifest should have flags in development mode',
);
});

it('overwrites existing manifest properties with override values but keeps original properties', () => {
mock.method(fs, 'readFileSync', (path: string, options: object) => {
if (path === resolve(__dirname, '../../../', manifestOverridesPath)) {
return JSON.stringify(mockFlags);
}
return fs.readFileSync.original(path, options);
});
const transform = transformManifest(
{ lockdown: true, test: false },
true,
manifestOverridesPath,
);
assert(transform, 'transform should be truthy');

const transformed = transform(notEmptyTestManifest, 'chrome');
assert.deepStrictEqual(
transformed,
{
_flags: {
remoteFeatureFlags: {
testFlag2: 'value1',
testFlag: true,
},
},
},
'manifest should merge original properties with overrides, with overrides taking precedence',
);
});

it('handles missing manifest flags file with path provided', () => {
mock.method(fs, 'readFileSync', () => {
const error = new Error('File not found') as NodeJS.ErrnoException;
error.code = 'ENOENT';
throw error;
});

const transform = transformManifest(
{ lockdown: true, test: false },
true,
manifestOverridesPath,
);
assert(transform, 'transform should be truthy');

assert.throws(
() => transform(emptyTestManifest, 'chrome'),
{
message: `Manifest override file not found: ${manifestOverridesPath}`,
},
'should throw when manifest override file is not found',
);
});

it('silently ignores non-ENOENT filesystem errors', () => {
const transform = transformManifest(
{ lockdown: true, test: false },
true,
manifestOverridesPath,
);
assert(transform, 'transform should be truthy');

const originalError = new Error(
'Permission denied',
) as NodeJS.ErrnoException;
originalError.code = 'EACCES';

mock.method(fs, 'readFileSync', () => {
throw originalError;
});

const transformed = transform(emptyTestManifest, 'chrome');
assert.deepStrictEqual(
transformed._flags,
undefined,
'should not have flags when file read fails with non-ENOENT error',
);
});
});
});
2 changes: 1 addition & 1 deletion development/webpack/test/webpack.config.test.ts
Original file line number Diff line number Diff line change
@@ -231,7 +231,7 @@ ${Object.entries(env)
assert.deepStrictEqual(manifestPlugin.options.description, null);
assert.deepStrictEqual(manifestPlugin.options.zip, true);
assert(manifestPlugin.options.zipOptions, 'Zip options should be present');
assert.strictEqual(manifestPlugin.options.transform, undefined);
assert.deepStrictEqual(manifestPlugin.options.transform, undefined);

const progressPlugin = instance.options.plugins.find(
(plugin) => plugin && plugin.constructor.name === 'ProgressPlugin',
62 changes: 57 additions & 5 deletions development/webpack/utils/plugins/ManifestPlugin/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import merge from 'lodash/merge';
/**
* Returns a function that will transform a manifest JSON object based on the
* given build args.
@@ -9,12 +10,20 @@
* @param args
* @param args.lockdown
* @param args.test
* @param isDevelopment
* @param manifestOverridesPath
* @returns a function that will transform the manifest JSON object
* @throws an error if the manifest already contains the "tabs" permission and
* `test` is `true`
*/
export function transformManifest(args: { lockdown: boolean; test: boolean }) {
const transforms: ((manifest: chrome.runtime.Manifest) => void)[] = [];
export function transformManifest(
args: { lockdown: boolean; test: boolean },
isDevelopment: boolean,
manifestOverridesPath?: string | undefined,
) {
const transforms: ((
manifest: chrome.runtime.Manifest,
) => chrome.runtime.Manifest | void)[] = [];

function removeLockdown(browserManifest: chrome.runtime.Manifest) {
const mainScripts = browserManifest.content_scripts?.[0];
@@ -29,6 +38,48 @@ export function transformManifest(args: { lockdown: boolean; test: boolean }) {
transforms.push(removeLockdown);
}

/**
* This function sets predefined flags in the manifest's _flags property
* that are stored in the file specified by the `MANIFEST_OVERRIDES` build variable
*
* @param browserManifest - The Chrome extension manifest object to modify
*/
function addManifestFlags(browserManifest: chrome.runtime.Manifest): void {
let manifestFlags;

if (manifestOverridesPath) {
try {
const fs = require('node:fs');
const path = require('node:path');
const manifestFlagsContent = fs.readFileSync(
path.resolve(process.cwd(), manifestOverridesPath),
'utf8',
);
manifestFlags = JSON.parse(manifestFlagsContent);
} catch (error: unknown) {
if (
error instanceof Error &&
'code' in error &&
error.code === 'ENOENT'
) {
// Only throw if ENOENT and manifestOverridesPath was provided
throw new Error(
`Manifest override file not found: ${manifestOverridesPath}`,
);
}
}
}

if (manifestFlags) {
merge(browserManifest, manifestFlags);
}
}

if (isDevelopment) {
// Add manifest flags only for development builds
transforms.push(addManifestFlags);
}

function addTabsPermission(browserManifest: chrome.runtime.Manifest) {
if (browserManifest.permissions) {
if (browserManifest.permissions.includes('tabs')) {
@@ -41,16 +92,17 @@ export function transformManifest(args: { lockdown: boolean; test: boolean }) {
browserManifest.permissions = ['tabs'];
}
}

if (args.test) {
// test builds need "tabs" permission for switchToWindowWithTitle
transforms.push(addTabsPermission);
}

return transforms.length
? (browserManifest: chrome.runtime.Manifest, _browser: string) => {
const clone = structuredClone(browserManifest);
transforms.forEach((transform) => transform(clone));
return clone;
const manifestClone = structuredClone(browserManifest);
transforms.forEach((transform) => transform(manifestClone));
return manifestClone;
}
: undefined;
}
6 changes: 5 additions & 1 deletion development/webpack/webpack.config.ts
Original file line number Diff line number Diff line change
@@ -131,7 +131,11 @@ const plugins: WebpackPluginInstance[] = [
version: version.version,
versionName: version.versionName,
browsers: args.browser,
transform: transformManifest(args),
transform: transformManifest(
args,
isDevelopment,
variables.get('MANIFEST_OVERRIDES') as string | undefined,
),
zip: args.zip,
...(args.zip
? {
Loading

Unchanged files with check annotations Beta

// TODO: Re think how to test this without exposing internal state
// it('should replace ethers instance when called with a different chainId than was current when the controller was instantiated', async function () {

Check warning on line 1265 in app/scripts/controllers/swaps/swaps.test.ts

GitHub Actions / Test lint / Test lint

Some tests seem to be commented
// fetchTradesInfoStub.mockReset();
// const _swapsController = getSwapsController();
// expect(currentEthersInstance).not.toStrictEqual(newEthersInstance);
// });
// it('should not replace ethers instance when called with the same chainId that was current when the controller was instantiated', async function () {

Check warning on line 1286 in app/scripts/controllers/swaps/swaps.test.ts

GitHub Actions / Test lint / Test lint

Some tests seem to be commented
// const _swapsController = new SwapsController({
// getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
// provider,
// expect(currentEthersInstance).toStrictEqual(newEthersInstance);
// });
// it('should replace ethers instance, and _ethersProviderChainId, twice when called twice with two different chainIds, and successfully set the _ethersProviderChainId when returning to the original chain', async function () {

Check warning on line 1306 in app/scripts/controllers/swaps/swaps.test.ts

GitHub Actions / Test lint / Test lint

Some tests seem to be commented
// const _swapsController = new SwapsController({
// getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
// provider,
});
});
// it('clears polling timeout', function () {

Check warning on line 1390 in app/scripts/controllers/swaps/swaps.test.ts

GitHub Actions / Test lint / Test lint

Some tests seem to be commented
// swapsController._pollingTimeout = setTimeout(() => {
// throw new Error('Polling timeout not cleared');
// }, POLLING_TIMEOUT);
describe('stopPollingForQuotes', function () {
// TODO: Re think how to test this without exposing internal state
// it('clears polling timeout', function () {

Check warning on line 1406 in app/scripts/controllers/swaps/swaps.test.ts

GitHub Actions / Test lint / Test lint

Some tests seem to be commented
// swapsController._pollingTimeout = setTimeout(() => {
// throw new Error('Polling timeout not cleared');
// }, POLLING_TIMEOUT);
describe('resetPostFetchState', function () {
// TODO: Re think how to test this without exposing internal state
// it('clears polling timeout', function () {

Check warning on line 1429 in app/scripts/controllers/swaps/swaps.test.ts

GitHub Actions / Test lint / Test lint

Some tests seem to be commented
// swapsController._pollingTimeout = setTimeout(() => {
// throw new Error('Polling timeout not cleared');
// }, POLLING_TIMEOUT);
image_large_url: '',
image_opengraph_url: '',
blurhash: 'U=Io~ufQ9_jtJTfQsTfQ0*fQ$$fQ#nfQX7fQ',
predominant_color: '#fb9f18',

Check warning on line 91 in test/e2e/flask/solana/common-solana.ts

GitHub Actions / Test lint / Test lint

'#fb9f18' Hex color values are not allowed. Consider using design tokens instead. For support reach out to the design system team #metamask-design-system on Slack
},
image_url: '',
image_properties: {
return null;
}
const t = useI18nContext();

Check warning on line 209 in ui/components/app/alert-system/alert-modal/alert-modal.tsx

GitHub Actions / Test lint / Test lint

React Hook "useI18nContext" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?
const severityStyle = getSeverityStyle(selectedAlert.severity);
return (
<Box
const isTestNetwork = useMemo(() => {
return (TEST_CHAINS as string[]).includes(currentNetwork.chainId);
}, [currentNetwork.chainId, TEST_CHAINS]);

Check warning on line 83 in ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx

GitHub Actions / Test lint / Test lint

React Hook useMemo has an unnecessary dependency: 'TEST_CHAINS'. Either exclude it or remove the dependency array. Outer scope values like 'TEST_CHAINS' aren't valid dependencies because mutating them doesn't re-render the component
const allOpts: Record<string, boolean> = {};
Object.keys(allNetworks || {}).forEach((chainId) => {
} else {
dispatch(setTokenNetworkFilter({ [currentNetwork.chainId]: true }));
}
}, []);

Check warning on line 109 in ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx

GitHub Actions / Test lint / Test lint

React Hook useEffect has missing dependencies: 'allOpts', 'currentNetwork.chainId', 'dispatch', and 'tokenNetworkFilter'. Either include them or remove the dependency array
// When a network gets added/removed we want to make sure that we switch to the filtered list of the current network
// We only want to do this if the "Current Network" filter is selected