diff --git a/lib/keyserver-conn/call-keyserver-endpoint-provider.react.js b/lib/keyserver-conn/call-keyserver-endpoint-provider.react.js --- a/lib/keyserver-conn/call-keyserver-endpoint-provider.react.js +++ b/lib/keyserver-conn/call-keyserver-endpoint-provider.react.js @@ -1,11 +1,15 @@ // @flow import invariant from 'invariant'; +import _memoize from 'lodash/memoize.js'; import * as React from 'react'; import { createSelector } from 'reselect'; import { useKeyserverCallInfos } from './keyserver-call-infos.js'; -import { setNewSession } from './keyserver-conn-types.js'; +import { + setNewSession, + type SingleKeyserverActionFunc, +} from './keyserver-conn-types.js'; import { canResolveKeyserverSessionInvalidation, resolveKeyserverSessionInvalidation, @@ -27,13 +31,21 @@ keyserverID: string, ) => ServerCallSelectorParams => CallSingleKeyserverEndpoint; -type GetCallSingleKeyserverEndpoint = ( +type GetBoundSingleKeyserverActionFunc = ( keyserverID: string, -) => CallSingleKeyserverEndpoint; + actionFunc: SingleKeyserverActionFunc, +) => F; + +type SingleKeyserverActionFuncSelectorParams = { + +callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, +}; +type CreateBoundSingleKeyserverActionFuncSelector = ( + actionFunc: SingleKeyserverActionFunc, +) => SingleKeyserverActionFuncSelectorParams => F; type CallKeyserverEndpointContextType = { +createCallSingleKeyserverEndpointSelector: CreateCallSingleKeyserverEndpointSelector, - +getCallSingleKeyserverEndpoint: GetCallSingleKeyserverEndpoint, + +getBoundSingleKeyserverActionFunc: GetBoundSingleKeyserverActionFunc, }; const CallKeyserverEndpointContext: React.Context = @@ -203,8 +215,8 @@ // For each keyserver, we have a set of params that configure our connection // to it. These params get bound into callSingleKeyserverEndpoint before it's - // passed to an ActionFunc. This helper function lets us create a selector for - // a given keyserverID that will regenerate the bound + // passed to a SingleKeyserverActionFunc. This helper function lets us create + // a selector for a given keyserverID that will regenerate the bound // callSingleKeyserverEndpoint function only if one of the params changes. // This lets us skip some React render cycles. const createCallSingleKeyserverEndpointSelector = React.useCallback( @@ -243,7 +255,7 @@ [bindCookieAndUtilsIntoCallSingleKeyserverEndpoint], ); - // SECTION 3: getCallSingleKeyserverEndpoint + // SECTION 3: getBoundSingleKeyserverActionFunc const dispatch = useDispatch(); const currentUserInfo = useSelector(state => state.currentUserInfo); @@ -256,39 +268,86 @@ const callSingleKeyserverEndpointSelectorCacheRef = React.useRef< Map CallSingleKeyserverEndpoint>, >(new Map()); - const getCallSingleKeyserverEndpoint: GetCallSingleKeyserverEndpoint = + const getCallSingleKeyserverEndpoint = React.useCallback( + (keyserverID: string) => { + let selector = + callSingleKeyserverEndpointSelectorCacheRef.current.get(keyserverID); + if (!selector) { + selector = createCallSingleKeyserverEndpointSelector(keyserverID); + callSingleKeyserverEndpointSelectorCacheRef.current.set( + keyserverID, + selector, + ); + } + const keyserverCallInfo = keyserverCallInfos[keyserverID]; + return selector({ + ...keyserverCallInfo, + dispatch, + currentUserInfo, + }); + }, + [ + createCallSingleKeyserverEndpointSelector, + dispatch, + currentUserInfo, + keyserverCallInfos, + ], + ); + + const createBoundSingleKeyserverActionFuncSelector: CreateBoundSingleKeyserverActionFuncSelector = React.useCallback( - (keyserverID: string) => { + actionFunc => + createSelector( + (params: SingleKeyserverActionFuncSelectorParams) => + params.callSingleKeyserverEndpoint, + actionFunc, + ), + [], + ); + + const createBoundSingleKeyserverActionFuncsCache: () => CreateBoundSingleKeyserverActionFuncSelector = + React.useCallback( + () => _memoize(createBoundSingleKeyserverActionFuncSelector), + [createBoundSingleKeyserverActionFuncSelector], + ); + + const boundSingleKeyserverActionFuncSelectorCacheRef = React.useRef< + Map, + >(new Map()); + const getBoundSingleKeyserverActionFunc: GetBoundSingleKeyserverActionFunc = + React.useCallback( + (keyserverID: string, actionFunc: SingleKeyserverActionFunc): F => { let selector = - callSingleKeyserverEndpointSelectorCacheRef.current.get(keyserverID); + boundSingleKeyserverActionFuncSelectorCacheRef.current.get( + keyserverID, + ); if (!selector) { - selector = createCallSingleKeyserverEndpointSelector(keyserverID); - callSingleKeyserverEndpointSelectorCacheRef.current.set( + selector = createBoundSingleKeyserverActionFuncsCache(); + boundSingleKeyserverActionFuncSelectorCacheRef.current.set( keyserverID, selector, ); } - const keyserverCallInfo = keyserverCallInfos[keyserverID]; - return selector({ - ...keyserverCallInfo, - dispatch, - currentUserInfo, + const callEndpoint = getCallSingleKeyserverEndpoint(keyserverID); + return selector(actionFunc)({ + callSingleKeyserverEndpoint: callEndpoint, }); }, [ - createCallSingleKeyserverEndpointSelector, - dispatch, - currentUserInfo, - keyserverCallInfos, + createBoundSingleKeyserverActionFuncsCache, + getCallSingleKeyserverEndpoint, ], ); const value = React.useMemo( () => ({ createCallSingleKeyserverEndpointSelector, - getCallSingleKeyserverEndpoint, + getBoundSingleKeyserverActionFunc, }), - [createCallSingleKeyserverEndpointSelector, getCallSingleKeyserverEndpoint], + [ + createCallSingleKeyserverEndpointSelector, + getBoundSingleKeyserverActionFunc, + ], ); return ( diff --git a/lib/utils/action-utils.js b/lib/utils/action-utils.js --- a/lib/utils/action-utils.js +++ b/lib/utils/action-utils.js @@ -16,18 +16,30 @@ serverCall: SingleKeyserverActionFunc, paramOverride?: ?Partial, ): F { - const dispatch = useDispatch(); - const serverCallState = useSelector( - serverCallStateSelector(ashoatKeyserverID), + const { + createCallSingleKeyserverEndpointSelector, + getBoundSingleKeyserverActionFunc, + } = useCallKeyserverEndpointContext(); + + const cachedNonOverridenBoundServerCall = React.useMemo( + () => getBoundSingleKeyserverActionFunc(ashoatKeyserverID, serverCall), + [getBoundSingleKeyserverActionFunc, serverCall], ); - const { createCallSingleKeyserverEndpointSelector } = - useCallKeyserverEndpointContext(); - const selector = React.useMemo( + + const customSelector = React.useMemo( () => createCallSingleKeyserverEndpointSelector(ashoatKeyserverID), [createCallSingleKeyserverEndpointSelector], ); + const dispatch = useDispatch(); + const serverCallState = useSelector( + serverCallStateSelector(ashoatKeyserverID), + ); return React.useMemo(() => { + if (!paramOverride) { + return cachedNonOverridenBoundServerCall; + } + const { urlPrefix, isSocketConnected } = serverCallState; invariant( !!urlPrefix && @@ -36,7 +48,7 @@ 'keyserver missing from keyserverStore', ); - const callSingleKeyserverEndpoint = selector({ + const callSingleKeyserverEndpoint = customSelector({ ...serverCallState, urlPrefix, isSocketConnected, @@ -44,7 +56,14 @@ ...paramOverride, }); return serverCall(callSingleKeyserverEndpoint); - }, [serverCall, serverCallState, dispatch, paramOverride, selector]); + }, [ + cachedNonOverridenBoundServerCall, + serverCall, + serverCallState, + dispatch, + paramOverride, + customSelector, + ]); } export { useLegacyAshoatKeyserverCall };