diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index 4b425085..70dd2a8d 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -110,7 +110,6 @@ const AnHostInputGroup = ({ buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, - setMsgSetter, }, hostId, hostNumber, @@ -152,8 +151,6 @@ const AnHostInputGroup = ({ const inputId = buildInputIdAHFencePort(hostId, fenceId); const inputLabel = `Port on ${fenceName}`; - setMsgSetter(inputId); - previous[cellId] = { children: ( ({ fenceListEntries, hostId, msgSetters, - setMsgSetter, ], ); @@ -209,8 +205,6 @@ const AnHostInputGroup = ({ const inputId = buildInputIdAHNetworkIp(hostId, networkId); const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; - setMsgSetter(inputId); - previous[cellId] = { children: ( ({ [ networkListEntries, hostId, - setMsgSetter, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, diff --git a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index 83c0c576..6f34c7f8 100644 --- a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -1,4 +1,4 @@ -import { ReactElement, ReactNode, useEffect, useMemo } from 'react'; +import { ReactElement, ReactNode, useMemo } from 'react'; import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; @@ -81,7 +81,6 @@ const AnNetworkInputGroup = ({ buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, - setMsgSetter, }, inputGatewayLabel = 'Gateway', inputMinIpLabel = 'IP address', @@ -170,8 +169,6 @@ const AnNetworkInputGroup = ({ let result: ReactNode; if (isShowGateway && inputIdGateway) { - setMsgSetter(inputIdGateway); - result = ( ({ }, [ isShowGateway, inputIdGateway, - setMsgSetter, networkId, inputGatewayLabel, previousGateway, @@ -219,11 +215,6 @@ const AnNetworkInputGroup = ({ msgSetters, ]); - useEffect(() => { - setMsgSetter(inputIdMinIp); - setMsgSetter(inputIdSubnetMask); - }, [inputIdMinIp, inputIdSubnetMask, setMsgSetter]); - return ( diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx index 1714c7d4..04e45a41 100644 --- a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -36,7 +36,6 @@ const RunManifestInputGroup = ({ buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, - setMsgSetter, }, knownFences = {}, knownHosts = {}, @@ -104,8 +103,6 @@ const RunManifestInputGroup = ({ const inputId = buildInputIdRMHost(hostId); const inputLabel = `${prettyId} host`; - setMsgSetter(inputId); - hosts[`run-manifest-host-cell-${hostId}`] = { children: ( ({ hostListEntries, hostOptionList, msgSetters, - setMsgSetter, ], ); diff --git a/striker-ui/hooks/useFormUtils.ts b/striker-ui/hooks/useFormUtils.ts index 8e40f965..2e5dff96 100644 --- a/striker-ui/hooks/useFormUtils.ts +++ b/striker-ui/hooks/useFormUtils.ts @@ -3,7 +3,9 @@ import { MutableRefObject, useCallback, useMemo, useState } from 'react'; import buildMapToMessageSetter, { buildMessageSetter, } from '../lib/buildMapToMessageSetter'; -import buildObjectStateSetterCallback from '../lib/buildObjectStateSetterCallback'; +import buildObjectStateSetterCallback, { + buildProtectedObjectStateSetterCallback, +} from '../lib/buildObjectStateSetterCallback'; import { MessageGroupForwardedRefContent } from '../components/MessageGroup'; const useFormUtils = < @@ -15,6 +17,9 @@ const useFormUtils = < messageGroupRef: MutableRefObject, ): FormUtils => { const [formValidity, setFormValidity] = useState>({}); + const [msgSetterList, setMsgSetterList] = useState>( + () => buildMapToMessageSetter(ids, messageGroupRef), + ); const setValidity = useCallback((key: keyof M, value?: boolean) => { setFormValidity( @@ -42,6 +47,37 @@ const useFormUtils = < }); }, []); + const setMsgSetter = useCallback( + ( + id: keyof M, + setter?: MessageSetter, + { + isOverwrite, + isUseFallback = true, + }: { isOverwrite?: boolean; isUseFallback?: boolean } = {}, + ) => { + const fallbackSetter: ObjectStatePropSetter> = ( + result, + key, + value = buildMessageSetter(String(id), messageGroupRef), + ) => { + result[key] = value; + }; + + setMsgSetterList( + buildProtectedObjectStateSetterCallback>( + id, + setter, + { + isOverwrite, + set: isUseFallback ? fallbackSetter : undefined, + }, + ), + ); + }, + [messageGroupRef], + ); + const buildFinishInputTestBatchFunction = useCallback( (key: keyof M) => (result: boolean) => { setValidity(key, result); @@ -52,9 +88,10 @@ const useFormUtils = < const buildInputFirstRenderFunction = useCallback( (key: keyof M) => ({ isValid }: InputFirstRenderFunctionArgs) => { + setMsgSetter(key); setValidity(key, isValid); }, - [setValidity], + [setMsgSetter, setValidity], ); const isFormInvalid = useMemo( @@ -62,27 +99,12 @@ const useFormUtils = < [formValidity], ); - const msgSetters = useMemo( - () => buildMapToMessageSetter(ids, messageGroupRef), - [ids, messageGroupRef], - ); - - const setMsgSetter = useCallback( - (id: keyof M, setter?: MessageSetter, isOverwrite?: boolean) => { - if (!msgSetters[id] || isOverwrite) { - msgSetters[id] = - setter ?? buildMessageSetter(String(id), messageGroupRef); - } - }, - [messageGroupRef, msgSetters], - ); - return { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, formValidity, isFormInvalid, - msgSetters, + msgSetters: msgSetterList, setFormValidity, setMsgSetter, setValidity, diff --git a/striker-ui/lib/buildObjectStateSetterCallback.ts b/striker-ui/lib/buildObjectStateSetterCallback.ts index 98cecfcf..85aca14f 100644 --- a/striker-ui/lib/buildObjectStateSetterCallback.ts +++ b/striker-ui/lib/buildObjectStateSetterCallback.ts @@ -1,13 +1,46 @@ +/** + * Checks whether specified `key` is unset in given object. Always returns + * `true` when overwrite is allowed. + */ +const checkUnset = ( + obj: S, + key: keyof S, + { isOverwrite = false }: { isOverwrite?: boolean } = {}, +): boolean => !(key in obj) || isOverwrite; + const buildObjectStateSetterCallback = - >(key: keyof S, value?: S[keyof S]) => - ({ [key]: toReplace, ...restPrevious }: S): S => { + ( + key: keyof S, + value?: S[keyof S], + { + guard, + set = (o, k, v) => { + if (v !== undefined) { + o[k] = v; + } + }, + }: BuildObjectStateSetterCallbackOptions = {}, + ): BuildObjectStateSetterCallbackReturnType => + (previous: S): S => { + const { [key]: toReplace, ...restPrevious } = previous; const result = { ...restPrevious } as S; - if (value !== undefined) { - result[key] = value; + if (guard?.call(null, previous, key, value)) { + set(result, key, value); } return result; }; +export const buildProtectedObjectStateSetterCallback = ( + key: keyof S, + value?: S[keyof S], + { + isOverwrite, + guard = (o, k) => checkUnset(o, k, { isOverwrite }), + set, + }: BuildObjectStateSetterCallbackOptions = {}, +): BuildObjectStateSetterCallbackReturnType => + buildObjectStateSetterCallback(key, value, { isOverwrite, guard, set }); + export default buildObjectStateSetterCallback; diff --git a/striker-ui/types/BuildObjectStateSetterCallback.d.ts b/striker-ui/types/BuildObjectStateSetterCallback.d.ts new file mode 100644 index 00000000..dd2aa60f --- /dev/null +++ b/striker-ui/types/BuildObjectStateSetterCallback.d.ts @@ -0,0 +1,23 @@ +type BaseObject = Record; + +type ObjectStatePropGuard = ( + previous: S, + key: keyof S, + value?: S[keyof S], +) => boolean; + +type ObjectStatePropSetter = ( + result: S, + key: keyof S, + value?: S[keyof S], +) => void; + +type BuildObjectStateSetterCallbackOptions = { + guard?: ObjectStatePropGuard; + isOverwrite?: boolean; + set?: ObjectStatePropSetter; +}; + +type BuildObjectStateSetterCallbackReturnType = ( + previous: S, +) => S; diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index 439c45a8..53f8382e 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -26,7 +26,7 @@ type FormUtils = { setMsgSetter: ( id: keyof M, setter?: MessageSetter, - isOverwrite?: boolean, + options?: { isOverwrite?: boolean }, ) => void; setValidity: (key: keyof M, value?: boolean) => void; setValidityRe: (re: RegExp, value?: boolean) => void;