I have the following forwarded ref in React:
export type AccordionHeaderProps<T extends ElementType> = {
children: React.ReactNode;
asTrigger?: boolean;
subTitle?: React.ReactNode;
chevronProps?: AriaButtonOptions<T>;
} & Options;
export const AccordionHeader = forwardRef(
(
{
children,
asTrigger,
subTitle,
chevronProps,
id,
className,
isVisible,
borderRadius,
isLoading,
error,
isDisabled
}: AccordionHeaderProps<"div">,
ref
) => {
const [accordionItemState, setAccordionItemState] = useContext(AccordionItemContext);
const innerRef = useRef(null);
useImperativeHandle(ref, () => innerRef.current!, []);
const { buttonProps } = useButton(chevronProps || {}, innerRef as React.RefObject<HTMLDivElement>);
const icon = [
<motion.div
animate={{ rotate: accordionItemState.isExpanded ? "-90deg" : "0deg" }}
whileHover={{ background: "#f1f5f9" }}
whileTap={{ scale: 0.9 }}
initial={{ background: "transparent" }}
transition={{ ease: "easeInOut" }}
className="ml-auto rounded-sm"
>
<ChevronLeft width={25} />
</motion.div>,
<div className="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid"
width="25"
height="25"
style={{ shapeRendering: "auto", display: "block", background: "transparent" }}
>
<g>
<circle
stroke-dasharray="122.52211349000194 42.840704496667314"
r="26"
stroke-width="5"
stroke="#000000"
fill="none"
cy="50"
cx="50"
>
<animateTransform
keyTimes="0;1"
values="0 50 50;360 50 50"
dur="1s"
repeatCount="indefinite"
type="rotate"
attributeName="transform"
></animateTransform>
</circle>
<g></g>
</g>
</svg>
</div>,
<div className="ml-auto">
<AlertCircle width={25} height={25} />
</div>
];
let iconState = 0;
if (isLoading) iconState = 1;
else if (error) iconState = 2;
const onClick = () => {
if (!asTrigger) return;
if (iconState > 0) return;
setAccordionItemState({ isExpanded: !accordionItemState.isExpanded });
if (accordionItemState.isExpanded) innerRef.current.removeAttribute("hidden");
};
return (
<div
{...buttonProps}
role="button"
id={id}
onClick={() => onClick()}
className={header({
class: `${className} ${borderRadius ? `rounded-${borderRadius}` : ""}`,
isDisabled,
isVisible
})}
>
<div>
{children}
<br />
<span className="text-slate-400">{subTitle}</span>
</div>
{icon[iconState]}
</div>
);
}
);
The part to pay attention to is innerRef
. innerRef.current
is null
. I want it to be a DOMRef. However, when I console log it I get null
for current. Even if a wrap it a useEffect
I still get the same result just printed twice.
What I want to be able to with the ref is remove a property from an element and add it back dynamically. I still want to be able to forward and pass a ref to useButton
. I would ideally like to accomplish this with full type safety (at least for the ref prop in the forward ref). How can I accomplish this.
I am using React 18. Client components, no framework. The useButton
hook comes from React aria.
Make sure your ref is passed down to your render output (assuming it's not part of buttonProps
). i.e.
return (
<div
{...buttonProps}
role="button"
ref={innerRef}
{ /* etc */ }
/>
);