diff --git a/src/components/form/field_password/field_password.tsx b/src/components/form/field_password/field_password.tsx index 17d4c6fb6a7..6d1c02edb7e 100644 --- a/src/components/form/field_password/field_password.tsx +++ b/src/components/form/field_password/field_password.tsx @@ -22,7 +22,6 @@ import React, { FunctionComponent, useState, Ref, - useCallback, } from 'react'; import { CommonProps } from '../../common'; import classNames from 'classnames'; @@ -35,6 +34,7 @@ import { import { EuiValidatableControl } from '../validatable_control'; import { EuiButtonIcon, EuiButtonIconProps } from '../../button'; import { useEuiI18n } from '../../i18n'; +import { useCombinedRefs } from '../../../services'; export type EuiFieldPasswordProps = InputHTMLAttributes & CommonProps & { @@ -102,18 +102,7 @@ export const EuiFieldPassword: FunctionComponent = ({ // Setup the inputRef to auto-focus when toggling visibility const [inputRef, _setInputRef] = useState(null); - const setInputRef = useCallback( - (ref: HTMLInputElement | null) => { - _setInputRef(ref); - if (typeof _inputRef === 'function') { - _inputRef(ref); - } else if (_inputRef) { - // @ts-ignore need to mutate current - _inputRef.current = ref; - } - }, - [_inputRef] - ); + const setInputRef = useCombinedRefs([_setInputRef, _inputRef]); const handleToggle = (isVisible: boolean) => { setInputType(isVisible ? 'password' : 'text'); diff --git a/src/services/hooks/index.ts b/src/services/hooks/index.ts index 94741454012..7a7b08a4b42 100644 --- a/src/services/hooks/index.ts +++ b/src/services/hooks/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export * from './useCombinedRefs'; export * from './useDependentState'; diff --git a/src/services/hooks/useCombinedRefs.ts b/src/services/hooks/useCombinedRefs.ts new file mode 100644 index 00000000000..f47cc265c44 --- /dev/null +++ b/src/services/hooks/useCombinedRefs.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MutableRefObject, Ref, useCallback } from 'react'; + +// For use when a component needs to set `ref` objects from multiple sources. +// For instance, if a component accepts a `ref` prop but also needs its own +// local reference for calculations, etc. +// This hook handles setting multiple `ref`s of any available `ref` type +// in a single callback function. + +export const useCombinedRefs = ( + refs: Array | MutableRefObject | undefined> +) => { + return useCallback( + (node: T) => + refs.forEach(ref => { + if (!ref) return; + + if (typeof ref === 'function') { + ref(node); + } else { + (ref as MutableRefObject).current = node; + } + }), + [refs] + ); +}; diff --git a/src/services/index.ts b/src/services/index.ts index 7409ce75601..4ca4aef8ab3 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -108,4 +108,4 @@ export { export { EuiWindowEvent } from './window_event'; -export { useDependentState } from './hooks'; +export { useCombinedRefs, useDependentState } from './hooks';