diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json
index eb91c87495..cedb13e5ab 100644
--- a/packages/snaps-controllers/coverage.json
+++ b/packages/snaps-controllers/coverage.json
@@ -1,6 +1,6 @@
{
- "branches": 93.51,
+ "branches": 93.34,
"functions": 97.36,
"lines": 98.33,
- "statements": 98.06
+ "statements": 98.07
}
diff --git a/packages/snaps-controllers/src/interface/utils.test.tsx b/packages/snaps-controllers/src/interface/utils.test.tsx
index 8265cb8e59..c6cdfdf72a 100644
--- a/packages/snaps-controllers/src/interface/utils.test.tsx
+++ b/packages/snaps-controllers/src/interface/utils.test.tsx
@@ -25,6 +25,7 @@ import {
getAssetSelectorStateValue,
getDefaultAsset,
getJsxInterface,
+ isStatefulComponent,
} from './utils';
import { MOCK_ACCOUNT_ID } from '../test-utils';
@@ -1222,3 +1223,45 @@ describe('getDefaultAsset', () => {
);
});
});
+
+describe('isStatefulComponent', () => {
+ it.each([
+ ,
+
+
+ ,
+
+ Option 1
+ ,
+ ,
+
+
+ Option 1
+
+ ,
+ ,
+ ,
+ ,
+ ])('returns true for "%p"', () => {
+ expect(isStatefulComponent()).toBe(true);
+ });
+
+ it('returns false for stateless components', () => {
+ expect(isStatefulComponent(foo)).toBe(false);
+ });
+
+ it('returns false for nested stateful components', () => {
+ expect(
+ isStatefulComponent(
+
+
+ ,
+ ),
+ ).toBe(false);
+ });
+});
diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts
index 2e5a321ccc..02e0fb7b6a 100644
--- a/packages/snaps-controllers/src/interface/utils.ts
+++ b/packages/snaps-controllers/src/interface/utils.ts
@@ -40,6 +40,41 @@ import {
parseCaipChainId,
} from '@metamask/utils';
+/**
+ * A list of stateful component types.
+ */
+const STATEFUL_COMPONENT_TYPES = [
+ 'Input',
+ 'Dropdown',
+ 'RadioGroup',
+ 'FileInput',
+ 'Checkbox',
+ 'Selector',
+ 'AssetSelector',
+ 'AddressInput',
+] as const;
+
+/**
+ * Type for stateful component types.
+ */
+type StatefulComponentType = (typeof STATEFUL_COMPONENT_TYPES)[number];
+
+/**
+ * Check if a component is a stateful component.
+ *
+ * @param component - The component to check.
+ * @param component.type - The type of the component.
+ *
+ * @returns Whether the component is a stateful component.
+ */
+export function isStatefulComponent(component: { type: string }): component is {
+ type: StatefulComponentType;
+} {
+ return STATEFUL_COMPONENT_TYPES.includes(
+ component.type as StatefulComponentType,
+ );
+}
+
/**
* A function to get the MultichainAssetController state.
*
@@ -366,18 +401,7 @@ export function constructState(
}
// Stateful components inside a form
- // TODO: This is becoming a bit of a mess, we should consider refactoring this.
- if (
- currentForm &&
- (component.type === 'Input' ||
- component.type === 'Dropdown' ||
- component.type === 'RadioGroup' ||
- component.type === 'FileInput' ||
- component.type === 'Checkbox' ||
- component.type === 'Selector' ||
- component.type === 'AssetSelector' ||
- component.type === 'AddressInput')
- ) {
+ if (currentForm && isStatefulComponent(component)) {
const formState = newState[currentForm.name] as FormState;
assertNameIsUnique(formState, component.props.name);
formState[component.props.name] = constructInputState(
@@ -390,17 +414,7 @@ export function constructState(
}
// Stateful components outside a form
- // TODO: This is becoming a bit of a mess, we should consider refactoring this.
- if (
- component.type === 'Input' ||
- component.type === 'Dropdown' ||
- component.type === 'RadioGroup' ||
- component.type === 'FileInput' ||
- component.type === 'Checkbox' ||
- component.type === 'Selector' ||
- component.type === 'AssetSelector' ||
- component.type === 'AddressInput'
- ) {
+ if (isStatefulComponent(component)) {
assertNameIsUnique(newState, component.props.name);
newState[component.props.name] = constructInputState(
oldState,