Just use one ref prop in Field.tsx

pull/28452/head
R Midhun Suresh 2024-11-13 16:35:02 +05:30
parent 86c6ba9dd7
commit e5c0cdc402
No known key found for this signature in database
2 changed files with 27 additions and 21 deletions

View File

@ -12,6 +12,8 @@ import React, {
RefObject, RefObject,
createRef, createRef,
ComponentProps, ComponentProps,
MutableRefObject,
RefCallback,
} from "react"; } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { debounce } from "lodash"; import { debounce } from "lodash";
@ -73,13 +75,9 @@ interface IProps {
// All other props pass through to the <input>. // All other props pass through to the <input>.
} }
type RefCallback<T extends HTMLElement> = (node: T | null) => void;
export interface IInputProps extends IProps, InputHTMLAttributes<HTMLInputElement> { export interface IInputProps extends IProps, InputHTMLAttributes<HTMLInputElement> {
// The ref pass through to the input // The ref pass through to the input
inputRef?: RefObject<HTMLInputElement>; inputRef?: RefObject<HTMLInputElement> | RefCallback<HTMLInputElement>;
// Ref callback that will be attached to the input. This takes precedence over inputRef.
refCallback?: RefCallback<HTMLInputElement>;
// The element to create. Defaults to "input". // The element to create. Defaults to "input".
element: "input"; element: "input";
// The input's value. This is a controlled component, so the value is required. // The input's value. This is a controlled component, so the value is required.
@ -88,9 +86,7 @@ export interface IInputProps extends IProps, InputHTMLAttributes<HTMLInputElemen
interface ISelectProps extends IProps, SelectHTMLAttributes<HTMLSelectElement> { interface ISelectProps extends IProps, SelectHTMLAttributes<HTMLSelectElement> {
// The ref pass through to the select // The ref pass through to the select
inputRef?: RefObject<HTMLSelectElement>; inputRef?: RefObject<HTMLSelectElement> | RefCallback<HTMLSelectElement>;
// Ref callback that will be attached to the input. This takes precedence over inputRef.
refCallback?: RefCallback<HTMLSelectElement>;
// To define options for a select, use <Field><option ... /></Field> // To define options for a select, use <Field><option ... /></Field>
element: "select"; element: "select";
// The select's value. This is a controlled component, so the value is required. // The select's value. This is a controlled component, so the value is required.
@ -99,9 +95,7 @@ interface ISelectProps extends IProps, SelectHTMLAttributes<HTMLSelectElement> {
interface ITextareaProps extends IProps, TextareaHTMLAttributes<HTMLTextAreaElement> { interface ITextareaProps extends IProps, TextareaHTMLAttributes<HTMLTextAreaElement> {
// The ref pass through to the textarea // The ref pass through to the textarea
inputRef?: RefObject<HTMLTextAreaElement>; inputRef?: RefObject<HTMLTextAreaElement> | RefCallback<HTMLTextAreaElement>;
// Ref callback that will be attached to the input. This takes precedence over inputRef.
refCallback?: RefCallback<HTMLTextAreaElement>;
element: "textarea"; element: "textarea";
// The textarea's value. This is a controlled component, so the value is required. // The textarea's value. This is a controlled component, so the value is required.
value: string; value: string;
@ -109,9 +103,7 @@ interface ITextareaProps extends IProps, TextareaHTMLAttributes<HTMLTextAreaElem
export interface INativeOnChangeInputProps extends IProps, InputHTMLAttributes<HTMLInputElement> { export interface INativeOnChangeInputProps extends IProps, InputHTMLAttributes<HTMLInputElement> {
// The ref pass through to the input // The ref pass through to the input
inputRef?: RefObject<HTMLInputElement>; inputRef?: RefObject<HTMLInputElement> | RefCallback<HTMLInputElement>;
// Ref callback that will be attached to the input. This takes precedence over inputRef.
refCallback?: RefCallback<HTMLInputElement>;
element: "input"; element: "input";
// The input's value. This is a controlled component, so the value is required. // The input's value. This is a controlled component, so the value is required.
value: string; value: string;
@ -128,7 +120,17 @@ interface IState {
export default class Field extends React.PureComponent<PropShapes, IState> { export default class Field extends React.PureComponent<PropShapes, IState> {
private readonly id: string; private readonly id: string;
private readonly _inputRef = createRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>(); private readonly _inputRef: MutableRefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null> =
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<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> = (node) => {
this._inputRef.current = node;
(this.props.inputRef as RefCallback<unknown>)(node);
};
public static readonly defaultProps = { public static readonly defaultProps = {
element: "input", element: "input",
@ -240,7 +242,12 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
} }
private get inputRef(): RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> { private get inputRef(): RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> {
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<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
} }
private onTooltipOpenChange = (open: boolean): void => { private onTooltipOpenChange = (open: boolean): void => {
@ -294,7 +301,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
const inputProps_: React.HTMLAttributes<HTMLSelectElement | HTMLInputElement | HTMLTextAreaElement> & const inputProps_: React.HTMLAttributes<HTMLSelectElement | HTMLInputElement | HTMLTextAreaElement> &
React.ClassAttributes<HTMLSelectElement | HTMLInputElement | HTMLTextAreaElement> = { React.ClassAttributes<HTMLSelectElement | HTMLInputElement | HTMLTextAreaElement> = {
...inputProps, ...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); const fieldInput = React.createElement(this.props.element, inputProps_, children);

View File

@ -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. 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 { _t } from "../../../languageHandler";
import Field from "../elements/Field"; import Field from "../elements/Field";
@ -23,7 +23,7 @@ const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {
const dateInputDefaultValue = formatDateForInput(date); const dateInputDefaultValue = formatDateForInput(date);
const [dateValue, setDateValue] = useState(dateInputDefaultValue); const [dateValue, setDateValue] = useState(dateInputDefaultValue);
const [onFocus, isActive, refCallback, inputRef] = useRovingTabIndex<HTMLInputElement>(); const [onFocus, isActive, refCallback] = useRovingTabIndex<HTMLInputElement>();
const onDateValueInput = (ev: React.ChangeEvent<HTMLInputElement>): void => setDateValue(ev.target.value); const onDateValueInput = (ev: React.ChangeEvent<HTMLInputElement>): void => setDateValue(ev.target.value);
const onJumpToDateSubmit = (ev: FormEvent): void => { const onJumpToDateSubmit = (ev: FormEvent): void => {
@ -45,8 +45,7 @@ const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {
className="mx_JumpToDatePicker_datePicker" className="mx_JumpToDatePicker_datePicker"
label={_t("room|jump_to_date_prompt")} label={_t("room|jump_to_date_prompt")}
onFocus={onFocus} onFocus={onFocus}
inputRef={inputRef as RefObject<HTMLInputElement>} inputRef={refCallback}
refCallback={refCallback}
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
/> />
<RovingAccessibleButton <RovingAccessibleButton