diff --git a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js index 4b5f19e0a014..6a812917306b 100644 --- a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js +++ b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js @@ -5,10 +5,7 @@ import { useSelector } from 'react-redux'; import { isEqual } from 'lodash'; import MetaMaskTemplateRenderer from '../../metamask-template-renderer/metamask-template-renderer'; import { SnapDelineator } from '../snap-delineator'; -import { - getSnapMetadata, - getMemoizedInterfaceContent, -} from '../../../../selectors'; +import { getSnapMetadata, getMemoizedInterface } from '../../../../selectors'; import { Box, FormTextField } from '../../../component-library'; import { DelineatorType } from '../../../../helpers/constants/snaps'; @@ -35,10 +32,15 @@ const SnapUIRendererComponent = ({ getSnapMetadata(state, snapId), ); - const content = useSelector((state) => - getMemoizedInterfaceContent(state, interfaceId), + const interfaceState = useSelector( + (state) => getMemoizedInterface(state, interfaceId), + // We only want to update the state if the content has changed. + // We do this to avoid useless re-renders. + (oldState, newState) => isEqual(oldState.content, newState.content), ); + const content = interfaceState?.content; + // sections are memoized to avoid useless re-renders if one of the parents element re-renders. const sections = useMemo( () => @@ -64,6 +66,8 @@ const SnapUIRendererComponent = ({ ); } + const { state: initialState, context } = interfaceState; + return ( - + {isPrompt && ( diff --git a/ui/contexts/snaps/snap-interface.tsx b/ui/contexts/snaps/snap-interface.tsx index 194128af4ebe..1943e35a2745 100644 --- a/ui/contexts/snaps/snap-interface.tsx +++ b/ui/contexts/snaps/snap-interface.tsx @@ -3,6 +3,7 @@ import { InterfaceState, UserInputEventType, } from '@metamask/snaps-sdk'; +import { Json } from '@metamask/utils'; import { debounce, throttle } from 'lodash'; import React, { FunctionComponent, @@ -11,8 +12,7 @@ import React, { useEffect, useRef, } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { getMemoizedInterface } from '../../selectors'; +import { useDispatch } from 'react-redux'; import { handleSnapRequest, updateInterfaceState, @@ -47,6 +47,8 @@ export const SnapInterfaceContext = export type SnapInterfaceContextProviderProps = { interfaceId: string; snapId: string; + initialState: Record | unknown>; + context: Json; }; // We want button clicks to be instant and therefore use throttling @@ -64,18 +66,14 @@ const THROTTLED_EVENTS = [ * @param params.children - The childrens to wrap with the context provider. * @param params.interfaceId - The interface ID to use. * @param params.snapId - The Snap ID that requested the interface. + * @param params.initialState - The initial state of the interface. + * @param params.context - The context blob of the interface. * @returns The context provider. */ export const SnapInterfaceContextProvider: FunctionComponent< SnapInterfaceContextProviderProps -> = ({ children, interfaceId, snapId }) => { +> = ({ children, interfaceId, snapId, initialState, context }) => { const dispatch = useDispatch(); - const { state: initialState, context } = useSelector( - (state) => getMemoizedInterface(state, interfaceId), - // Prevents the selector update. - // We do this to avoid useless re-renders. - () => true, - ); // We keep an internal copy of the state to speed-up the state update in the UI. // It's kept in a ref to avoid useless re-rendering of the entire tree of components.