-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
@react-aria: forwardRef support #834
Comments
One pattern we've used is something like this: const Button = React.forwardRef((props, ref) => {
let fallbackRef = React.useRef();
let domRef = ref || fallbackRef;
return (
<button ref={domRef}>button</button>
);
}); This uses the passed ref if available, but falls back to a ref created by |
Yes, thatβs exactly what I was thinking! Thank you for the example π either a utility hook or a mention in the docs would be a nice addition |
Sounds good! We could add a |
Just keep in mind that you might often need access to the |
@devongovett I'm not sure how to use your advice here on making a new version of, something like |
Those props only exist because in the past there was no The thing here gets complicated because |
@Andarist Thanks for the reply. I'm down with using |
It was an intentional choice to not expose the raw DOM node from our refs, but wrap in an API we control. This allows us to swap out the underlying implementation when needed, e.g. switch element types. Also allows us to reuse the same API across multiple platforms, e.g. native, where the internal elements are different. Sometimes it's also unclear where the There is a If you need a ref object to pass the native input to something else, you could do something like this: let textfieldRef = useRef();
let inputRef = {
get current() {
return textFieldRef.current.getInputElement();
}
};
// Pass inputRef to whatever you like...
<TextField ref={textFieldRef} /> Perhaps there could be a util function to do this... |
Gotcha. Thanks! Turns out that at least for react-hook-form you can us a Controller and manage it without a ref. |
Also having trouble with React's forwardRef and |
The ref exposed by the TextField component does have a focus() method already... |
I'm having trouble with the type error that the ref from |
Similar situation: I'm attempting to forward a ref to a component using the import React from "react";
import { useButton } from "@react-aria/button";
const Button = React.forwardRef((props, forwardedRef) => {
let internalRef = React.useRef();
let ref = forwardedRef || internalRef;
let { buttonProps } = useButton(props, ref);
return (
<button {...buttonProps} ref={ref}>
{props.children}
</button>
);
});
export default Button; The
|
We are trying to build a component library using react-aria and are running into trouble due to the types from TypeScript. Being unable to wrap this hook inside Like skywinston the "workaround" doesn't satisfy the types for Is the Edit: I was able to make this "work" by asserting that the ref is a mutable object ref function assertOnlyObjectRef<RefType>(
ref: ForwardedRef<RefType>
): asserts ref is MutableRefObject<RefType | null> | null {
if (typeof ref === 'function') {
throw new Error('This component may only be used with an object ref');
}
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{ children, size = Size.M, style = Style.Default, ...buttonHookProps },
forwardRef
) => {
assertOnlyObjectRef(forwardRef);
const internalRef = useRef<HTMLButtonElement>(null);
const ref = forwardRef ?? internalRef;
const { buttonProps } = useButton(buttonHookProps, ref);
return (
<button
className={clsx(styles.button, styles[size], styles[style])}
ref={ref}
{...buttonProps}
>
{children}
</button>
);
}
); Is this the expected way to use |
I'm curious, we use it just fine in our own component without that assertion I'm no typescript expert though, really just a user, so maybe we've made some error in our own? |
@snowystinger I'd say it's due to strict mode being enabled in the tsconfig.json |
I think it could also be due to this hook
It returns a mutable object ref from The other issue I see is that Button.tsx casts Basically that Button.tsx is instructing typescript that it has a different signature that React.forwardRef actually defined The crux of the issue My assertion narrows the type of the forward ref to only be a RefObject which satisfies the types of useButton I imagine if the casting was removed from the Button.tsx example you shared it would expose a similar type error as described by myself and skywinston |
@snowystinger I have modified Button.tsx to be a more typical / common usage of forwardRef and it exposes the same problem I will be clear I understand that the casting is to support a generic button ElemenType which otherwise can't be supported with Passing a ref function is a normal and supported way of retereving a ref from a component, one which useButton (and I presume a lot of other hooks in the react-aria codebase) don't support. I hope this diff illustates the issue clearly in the react-spectum codebase. Please let me know if you have further questions or there is anything that I may clearify! diff --git a/packages/@react-spectrum/button/src/Button.tsx b/packages/@react-spectrum/button/src/Button.tsx
index 6dea9cde..ed2f3ff3 100644
--- a/packages/@react-spectrum/button/src/Button.tsx
+++ b/packages/@react-spectrum/button/src/Button.tsx
@@ -10,24 +10,30 @@
* governing permissions and limitations under the License.
*/
-import {classNames, SlotProvider, useFocusableRef, useSlotProps, useStyleProps} from '@react-spectrum/utils';
-import {FocusableRef} from '@react-types/shared';
-import {FocusRing} from '@react-aria/focus';
-import {mergeProps} from '@react-aria/utils';
-import React, {ElementType, ReactElement} from 'react';
-import {SpectrumButtonProps} from '@react-types/button';
+import { classNames, SlotProvider, useFocusableRef, useSlotProps, useStyleProps } from '@react-spectrum/utils';
+import { FocusableRef } from '@react-types/shared';
+import { FocusRing } from '@react-aria/focus';
+import { mergeProps } from '@react-aria/utils';
+import React, { ElementType, ReactElement } from 'react';
+import { SpectrumButtonProps } from '@react-types/button';
import styles from '@adobe/spectrum-css-temp/components/button/vars.css';
-import {Text} from '@react-spectrum/text';
-import {useButton} from '@react-aria/button';
-import {useHover} from '@react-aria/interactions';
-import {useProviderProps} from '@react-spectrum/provider';
+import { Text } from '@react-spectrum/text';
+import { useButton } from '@react-aria/button';
+import { useHover } from '@react-aria/interactions';
+import { useProviderProps } from '@react-spectrum/provider';
// todo: CSS hasn't caught up yet, map
let VARIANT_MAPPING = {
negative: 'warning'
};
-function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>, ref: FocusableRef<HTMLElement>) {
+
+/**
+ * Buttons allow users to perform an action or to navigate to another page.
+ * They have multiple styles for various needs, and are ideal for calling attention to
+ * where a user needs to do something in order to move forward in a flow.
+ */
+let _Button = React.forwardRef<HTMLElement, SpectrumButtonProps<'button'>>((props, ref) => {
props = useProviderProps(props);
props = useSlotProps(props, 'button');
let {
@@ -39,10 +45,10 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
autoFocus,
...otherProps
} = props;
- let domRef = useFocusableRef(ref);
- let {buttonProps, isPressed} = useButton(props, domRef);
- let {hoverProps, isHovered} = useHover({isDisabled});
- let {styleProps} = useStyleProps(otherProps);
+ // let domRef = useFocusableRef(ref);
+ let { buttonProps, isPressed } = useButton(props, ref);
+ let { hoverProps, isHovered } = useHover({ isDisabled });
+ let { styleProps } = useStyleProps(otherProps);
let buttonVariant = variant;
if (VARIANT_MAPPING[variant]) {
@@ -86,12 +92,5 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
</ElementType>
</FocusRing>
);
-}
-
-/**
- * Buttons allow users to perform an action or to navigate to another page.
- * They have multiple styles for various needs, and are ideal for calling attention to
- * where a user needs to do something in order to move forward in a flow.
- */
-let _Button = React.forwardRef(Button) as <T extends ElementType = 'button'>(props: SpectrumButtonProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
-export {_Button as Button};
+});
+export { _Button as Button }; |
What's the status of this issue? not to be rude, but it gets impossible to integrate |
I managed to find a solution via useImperativeHandle: const Checkbox = React.forwardRef(
(props: PropsWithChildren<CheckboxProps>, ref: React.ForwardedRef<HTMLInputElement>) => {
const inputRef = React.useRef<HTMLInputElement>(null);
const { inputProps } = useCheckbox(props, state, inputRef);
React.useImperativeHandle(ref, getCurrentRef(inputRef));
return (
<CheckboxWrapper isValid={isValid} isDisabled={isDisabled}>
<VisuallyHidden>
<input {...inputProps} {...focusProps} ref={inputRef} />
</VisuallyHidden>
...
</CheckboxWrapper>
);
}
);
export function getCurrentRef<T>(ref: React.RefObject<T>) {
return () => {
if (ref.current === null) {
throw new Error("getCurrentRef should only be used when ref is certainly defined");
}
return ref.current;
};
} |
Handled in #2293, should go out in the next release |
Did we do this yet? #2293 (comment) |
Ah my bad, reopening. For others in the thread, |
@devongovett, @LFDanLu - I've created #2503 to track the ask in #2293 (comment). I think we can close this since we have a way to address the original ask, albeit not ideal. |
const TextField = React.forwardRef((props, forwardedRef) => {
const ref = useObjectRef(forwardedRef);
return <input {...props} ref={ref} />;
}); |
@jlarmstrongiv I can't speak to the question in your second bullet point, but I don't think it has been released yet. I've been copying the file at this commit and pasting it into my projects until it is published properly. Not ideal, but I don't mind using it as a stop-gap until the next release of react-aria. Your usage example is how I've been using it. |
@jlarmstrongiv It is not currently available in the current release but it will be with the next release (3.15). For now you can access it via a the |
Followup task tracked in #2503. Closing this ticket. |
πββοΈ Feature Request
Support for React.forwardRef on custom form elements to accommodate:
π€ Expected Behavior
π― Current Behavior
πββοΈ Possible Solution / π» Examples
Implement a utility to share / combine refs. Several similar implementations already exist:
π¦ Context
I would like to build robust and accessible custom form components that support both controlled and uncontrolled component patterns.
The text was updated successfully, but these errors were encountered: