diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2022-10-04 11:09:32 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-10-04 12:46:44 +0200 |
| commit | a739838f13d4cf7b79391f71dc5b00cd46b688f6 (patch) | |
| tree | de099992e70456e473dd38bfe28d688222f15ef6 /gui/src | |
| parent | a77933349760cd0152ff1aab1faabbb1193f538a (diff) | |
| download | mullvadvpn-a739838f13d4cf7b79391f71dc5b00cd46b688f6.tar.xz mullvadvpn-a739838f13d4cf7b79391f71dc5b00cd46b688f6.zip | |
Make Input component both controlled and uncontrolled depending on props
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/components/OpenVpnSettings.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardSettings.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/cell/Input.tsx | 56 |
3 files changed, 37 insertions, 23 deletions
diff --git a/gui/src/renderer/components/OpenVpnSettings.tsx b/gui/src/renderer/components/OpenVpnSettings.tsx index 69a570c356..7c842f6c06 100644 --- a/gui/src/renderer/components/OpenVpnSettings.tsx +++ b/gui/src/renderer/components/OpenVpnSettings.tsx @@ -465,7 +465,7 @@ function MssFixSetting() { </AriaLabel> <AriaInput> <Cell.AutoSizingTextInput - value={mssfix ? mssfix.toString() : ''} + initialValue={mssfix ? mssfix.toString() : ''} inputMode={'numeric'} maxLength={4} placeholder={messages.gettext('Default')} diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 7adcba6140..dea8c6dbc2 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -526,7 +526,7 @@ function MtuSetting() { </AriaLabel> <AriaInput> <Cell.AutoSizingTextInput - value={mtu ? mtu.toString() : ''} + initialValue={mtu ? mtu.toString() : ''} inputMode={'numeric'} maxLength={4} placeholder={messages.gettext('Default')} diff --git a/gui/src/renderer/components/cell/Input.tsx b/gui/src/renderer/components/cell/Input.tsx index 129d2f8f95..bff8364d21 100644 --- a/gui/src/renderer/components/cell/Input.tsx +++ b/gui/src/renderer/components/cell/Input.tsx @@ -38,6 +38,7 @@ const StyledInput = styled.input({}, (props: { focused: boolean; valid?: boolean interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> { value?: string; + initialValue?: string; validateValue?: (value: string) => boolean; modifyValue?: (value: string) => string; submitOnBlur?: boolean; @@ -46,8 +47,12 @@ interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> { onChangeValue?: (value: string) => void; } +// If value is provided this component behaves like a controlled component. +// If value isn't provided, then initialValue will be used for the initial value, but updates to +// initialValue will also cause the internal value to update. function InputWithRef(props: IInputProps, forwardedRef: React.Ref<HTMLInputElement>) { const { + initialValue, validateValue, modifyValue, submitOnBlur, @@ -57,17 +62,20 @@ function InputWithRef(props: IInputProps, forwardedRef: React.Ref<HTMLInputEleme ...otherProps } = props; - const [value, setValue] = useState(props.value ?? ''); const [isFocused, setFocused, setBlurred] = useBoolean(false); + // internalValue will be used when the component is uncontrolled. + const [internalValue, setInternalValue] = useState(props.value ?? props.initialValue ?? ''); + const value = props.value ?? internalValue; + const inputRef = useRef() as React.RefObject<HTMLInputElement>; const combinedRef = useCombinedRefs(inputRef, forwardedRef); const onSubmit = useCallback( (value: string) => { - if (validateValue?.(value) !== false && submitOnBlur) { + if (validateValue?.(value) !== false) { onSubmitValue?.(value); - } else if (submitOnBlur) { + } else { onInvalidValue?.(value); } }, @@ -86,7 +94,9 @@ function InputWithRef(props: IInputProps, forwardedRef: React.Ref<HTMLInputEleme (event: React.FocusEvent<HTMLInputElement>) => { setBlurred(); props.onBlur?.(event); - onSubmit(value); + if (submitOnBlur) { + onSubmit(value); + } }, [value, props.onBlur, validateValue, onSubmit, submitOnBlur], ); @@ -94,11 +104,16 @@ function InputWithRef(props: IInputProps, forwardedRef: React.Ref<HTMLInputEleme const onChange = useCallback( (event: React.ChangeEvent<HTMLInputElement>) => { const value = modifyValue?.(event.target.value) ?? event.target.value; - setValue(value); + if (props.value === undefined) { + // Only update the internal value when in uncontrolled mode to not cause unnecessary render + // cycles. + setInternalValue(value); + } + props.onChange?.(event); onChangeValue?.(value); }, - [value, modifyValue, props.onSubmit], + [modifyValue, props.onSubmit], ); const onKeyPress = useCallback( @@ -112,12 +127,19 @@ function InputWithRef(props: IInputProps, forwardedRef: React.Ref<HTMLInputEleme [value, onSubmit, inputRef, props.onKeyPress], ); + // If the the initialValue changes in the uncontrolled mode when the user isn't currently writing, + // then we want to update the value. useEffect(() => { - if (!isFocused && props.value !== undefined && value !== props.value) { - setValue(props.value); - onChangeValue?.(props.value); + if ( + !isFocused && + props.value === undefined && + props.initialValue !== undefined && + internalValue !== props.initialValue + ) { + setInternalValue(props.initialValue); + onChangeValue?.(props.initialValue); } - }, [props.value]); + }, [props.initialValue]); const valid = validateValue?.(value); @@ -172,21 +194,12 @@ const StyledAutoSizingTextInputWrapper = styled.div({ }); function AutoSizingTextInputWithRef(props: IInputProps, forwardedRef: React.Ref<HTMLInputElement>) { - const { onChangeValue, onFocus, onBlur, ...otherProps } = props; + const { onFocus, onBlur, ...otherProps } = props; - const [value, setValue] = useState(otherProps.value ?? ''); const [focused, setFocused, setBlurred] = useBoolean(false); const inputRef = useRef() as React.RefObject<HTMLInputElement>; const combinedRef = useCombinedRefs(inputRef, forwardedRef); - const onChangeValueWrapper = useCallback( - (value: string) => { - setValue(value); - onChangeValue?.(value); - }, - [onChangeValue], - ); - const onBlurWrapper = useCallback( (event: React.FocusEvent<HTMLInputElement>) => { setBlurred(); @@ -205,6 +218,8 @@ function AutoSizingTextInputWithRef(props: IInputProps, forwardedRef: React.Ref< const blur = useCallback(() => inputRef.current?.blur(), []); + const value = inputRef.current?.value; + return ( <BackAction disabled={!focused} action={blur}> <InputFrame focused={focused}> @@ -212,7 +227,6 @@ function AutoSizingTextInputWithRef(props: IInputProps, forwardedRef: React.Ref< <StyledAutoSizingTextInputWrapper> <Input ref={combinedRef} - onChangeValue={onChangeValueWrapper} onBlur={onBlurWrapper} onFocus={onFocusWrapper} {...otherProps} |
