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}
/>