diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index cbd1ef2e36..789987d8ac 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -12,6 +12,8 @@ import React, { RefObject, createRef, ComponentProps, + MutableRefObject, + RefCallback, } from "react"; import classNames from "classnames"; import { debounce } from "lodash"; @@ -73,13 +75,9 @@ interface IProps { // All other props pass through to the . } -type RefCallback = (node: T | null) => void; - export interface IInputProps extends IProps, InputHTMLAttributes { // The ref pass through to the input - inputRef?: RefObject; - // Ref callback that will be attached to the input. This takes precedence over inputRef. - refCallback?: RefCallback; + inputRef?: RefObject | RefCallback; // The element to create. Defaults to "input". element: "input"; // The input's value. This is a controlled component, so the value is required. @@ -88,9 +86,7 @@ export interface IInputProps extends IProps, InputHTMLAttributes { // The ref pass through to the select - inputRef?: RefObject; - // Ref callback that will be attached to the input. This takes precedence over inputRef. - refCallback?: RefCallback; + inputRef?: RefObject | RefCallback; // To define options for a select, use element: "select"; // The select's value. This is a controlled component, so the value is required. @@ -99,9 +95,7 @@ interface ISelectProps extends IProps, SelectHTMLAttributes { interface ITextareaProps extends IProps, TextareaHTMLAttributes { // The ref pass through to the textarea - inputRef?: RefObject; - // Ref callback that will be attached to the input. This takes precedence over inputRef. - refCallback?: RefCallback; + inputRef?: RefObject | RefCallback; element: "textarea"; // The textarea's value. This is a controlled component, so the value is required. value: string; @@ -109,9 +103,7 @@ interface ITextareaProps extends IProps, TextareaHTMLAttributes { // The ref pass through to the input - inputRef?: RefObject; - // Ref callback that will be attached to the input. This takes precedence over inputRef. - refCallback?: RefCallback; + inputRef?: RefObject | RefCallback; element: "input"; // The input's value. This is a controlled component, so the value is required. value: string; @@ -128,7 +120,17 @@ interface IState { export default class Field extends React.PureComponent { private readonly id: string; - private readonly _inputRef = createRef(); + private readonly _inputRef: MutableRefObject = + createRef(); + + /** + * When props.inputRef is a callback ref, we will pass callbackRef to the DOM element. + * This is so that other methods here can still access the DOM element via this._inputRef. + */ + private readonly callbackRef: RefCallback = (node) => { + this._inputRef.current = node; + (this.props.inputRef as RefCallback)(node); + }; public static readonly defaultProps = { element: "input", @@ -240,7 +242,12 @@ export default class Field extends React.PureComponent { } private get inputRef(): RefObject { - return this.props.inputRef ?? this._inputRef; + const inputRef = this.props.inputRef; + if (typeof inputRef === "function") { + // This is a callback ref, so return _inputRef which will point to the actual DOM element. + return this._inputRef; + } + return (inputRef ?? this._inputRef) as RefObject; } private onTooltipOpenChange = (open: boolean): void => { @@ -294,7 +301,7 @@ export default class Field extends React.PureComponent { const inputProps_: React.HTMLAttributes & React.ClassAttributes = { ...inputProps, - ref: this.props.refCallback ?? this.inputRef, + ref: typeof this.props.inputRef === "function" ? this.callbackRef : this.inputRef, }; const fieldInput = React.createElement(this.props.element, inputProps_, children); diff --git a/src/components/views/messages/JumpToDatePicker.tsx b/src/components/views/messages/JumpToDatePicker.tsx index a5f2a8d0c4..4f4f0a6e05 100644 --- a/src/components/views/messages/JumpToDatePicker.tsx +++ b/src/components/views/messages/JumpToDatePicker.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React, { useState, FormEvent, RefObject } from "react"; +import React, { useState, FormEvent } from "react"; import { _t } from "../../../languageHandler"; import Field from "../elements/Field"; @@ -23,7 +23,7 @@ const JumpToDatePicker: React.FC = ({ ts, onDatePicked }: IProps) => { const dateInputDefaultValue = formatDateForInput(date); const [dateValue, setDateValue] = useState(dateInputDefaultValue); - const [onFocus, isActive, refCallback, inputRef] = useRovingTabIndex(); + const [onFocus, isActive, refCallback] = useRovingTabIndex(); const onDateValueInput = (ev: React.ChangeEvent): void => setDateValue(ev.target.value); const onJumpToDateSubmit = (ev: FormEvent): void => { @@ -45,8 +45,7 @@ const JumpToDatePicker: React.FC = ({ ts, onDatePicked }: IProps) => { className="mx_JumpToDatePicker_datePicker" label={_t("room|jump_to_date_prompt")} onFocus={onFocus} - inputRef={inputRef as RefObject} - refCallback={refCallback} + inputRef={refCallback} tabIndex={isActive ? 0 : -1} />