I’ve been using React 19, and with the changes to how ref
is handled, I noticed I need to adjust my code for components that inherit standard HTML element attributes.
Previously, I used forwardRef
like this, and it worked perfectly, passing all attributes of a <div>
to the component.
Using forwardRef
export const Section = forwardRef<
HTMLDivElement,
AllHTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div {...props} className={clsx("", className)} ref={ref} />
));
Section.displayName = "Section";
Using Ref
export const Section = ({
className,
ref,
...props
}: {
className?: string;
children: ReactNode;
ref?: Ref<HTMLDivElement>;
}) => <div className={clsx("section", className)} ref={ref} {...props} />;
Now, to achieve the same behavior, I have to write something more verbose, creating an interface
for each component:
interface SectionProps extends AllHTMLAttributes<HTMLDivElement> {
className?: string;
children?: ReactNode;
ref?: Ref<HTMLDivElement>;
}
This works, but it makes the code more repetitive. My question is: if I have multiple components like Card
, CardHeader
, CardTitle
, CardContent
, etc., do I need to create an interface for each one of them?
Any ideas on how to fix this?
I’m looking for a way to simplify this process while keeping the TypeScript type safety without having to define a separate interface for every single component.
What I’ve tried:
- Checked the latest React 19 documentation.
- Considered creating a base interface to reuse, but I’m not sure if that’s the best practice.
2
Answers
While exploring how to handle HTML attributes and
ref
properly in React 19, I found an efficient solution to my question.The Problem
The challenge was enabling a component to receive standard HTML attributes like
className
andid
while properly handling the ref following the changes introduced in React 19.The Solution
I created a base interface that simplifies reuse and ensures that all attributes are correctly assigned. Here's how it works:
Defining a Base Interface
This interface:
AllHTMLAttributes<T>
: Inherits all standard HTML attributes for the specified type.className
,children
, andref
, as needed.Applying the Interface in a Component
With the interface defined, you just need to specify the type of HTML element in the component for correct behavior:
Why It Works Great
Final Thoughts
This solution has worked well for me, and I hope it helps other developers with similar challenges.
Generally speaking you’d very likely want separate interfaces for all these components, but you can abstract the common props they share into an interface they all can extend before adding their specific properties.
children
,classname
, andref
are all common React props, you could create an interface that includes these by default using React’sPropsWithChildren
type:If you didn’t want to "lock" yourself into only using all HTML attributes on a
div
element you can make your common interface generic instead:Using a
div
element implementation (the default/fallback type):Using a
span
element implementation: