In this article, we will explore when and how to use ComponentPropsWithoutRef
with real use cases. One of the key features of React is the ability to create reusable components that can be used throughout an application. When creating a component, it is often necessary to define the props that the component will accept and use. Use of typescript in React is to ensure strong type safety and simplifying prop management in your React components.
Table of Contents
What is ComponentPropsWithoutRef
?
ComponentPropsWithoutRef is a utility type provided by React TypeScrip that can be used to define the props of a component. The ComponentPropsWithoutRef
type is useful when you want to define the props of a component, but you don’t want to include the `ref` prop. The `ref` prop is a special prop in React that is used to get a reference to a component instance.
In most cases, you won’t need to include the `ref` prop in your component’s props, so using `ComponentPropsWithoutRef` can help make your code cleaner and easier to read. The ComponentPropsWithoutRef
type in React TypeScript is a powerful tool that allows developers to extract the props of native HTML elements and ensure type safety.
When to use ComponentPropsWithoutRef
ComponentPropsWithoutRef
is specifically designed for those scenarios where you want to work with a component’s props while intentionally excluding the ref
prop. Main use of ComponentPropsWithoutRef
helps you work with props safely and intentionally, preventing unintended side effects when refs are involved. When to used componentPropsWithoutRef
- Extending Native Elements: Prevent accidental usage of invalid props, in our first example
- Creating wrapper component
- Creating Polymorphic Components, Render different elements based on user-defined props.
In previous example on articles React TypeScript: Leveraging the ‘&’ Operator for Wrapper Component Props where we use ComponentPropsWithoutRef to get all attribute from Input element without define it and additional we add two extra props like Label and id.
Extending Native Elements – ComponentPropsWithoutRef example
Let demonstrate an example of button component, where we use ComponentPropsWithoutRef
utility ensures that only valid button
props are used, preventing potential errors. We can customize it by allowing extra props text along with all HTML button attributes. The & { text: string; }
extends the Props
type with an additional text
property of type string
, specifically for the MyButton
component.
import React from 'react';
type Props = React.ComponentPropsWithoutRef<'button'> & {
text: string;
};
const MyButton = ({ text, ...rest }: Props) => {
return <button {...rest}>{text}</button>;
};
The ComponentPropsWithoutRef type can be used to grab all the native attributes of an HTML element as the props type of your component. In our case we used button, we can used all native HTML element. In our parent component we can called our custom button component. We are using `ComponentPropsWithoutRef` to exclude the `ref` prop from the list of props that our component accepts.
const ParentComponent = () => {
return (
<div>
<MyButton text="Click Me" className="my-button" />
<MyButton text="Another Action" disabled />
</div>
);
};
Same example with different version using children, onClick and interface instead of type.
interface ButtonProps {
variant: 'primary' | 'secondary';
onClick?: () => void;
}
const MyButton: React.FC<ButtonProps> = ({ children, variant, onClick, ...rest }) => {
return (
<button {...rest} onClick={onClick}>{children}</button> // Pass onClick to button
);
};
const Usage: React.FC = () => {
const handleClick = () => {
console.log('Button clicked!');
};
return (
<MyButton variant="primary" className="my-button" onClick={handleClick}>
Click me
</MyButton>
);
};
Creating Wrapper Components
Let’s consider a real-world scenario where ComponentPropsWithoutRef
can be useful. Imagine you have a utility function or a higher-order component (HOC) that needs to inject additional props into a given component but should not interfere with the ref handling. Here we have wrapper component called Card, a accepts a prop called `header` which is a string representing the title of the card, and a `children` prop which is the content of the card.
interface CardProps extends React.ComponentPropsWithoutRef<'div'> {
header: string;
}
const Card: React.FC<CardProps> = ({ header, children, ...rest }) => {
return (
<div {...rest}>
<h2>{header}</h2>
{children}
</div>
);
};
const Usage: React.FC = () => {
return (
<Card header="My Card">
<p>Some content</p>
</Card>
);
};
Creating Polymorphic Components
Creating polymorphic components in React involves designing components that can work with various underlying HTML elements or other components. Render different elements based on user-defined props.
We are creating usage component where we have demonstrating polymorphic component using componentWithoutPropsRef. The Usage.tsx is parent component.
import React from 'react';
import Card from './Card';
import Text from './Text';
import Header from './Header';
import Footer from './Footer';
const Usage: React.FC = () => {
return (
<>
<Card>
<h2>Title</h2>
<p>Content goes here.</p>
</Card>
<Card as="section">
<img src="image.jpg" alt="A beautiful image" />
<p>More content here.</p>
</Card>
<Header>
<h1>Header</h1>
</Header>
<Footer>
<p>Footer content</p>
</Footer>
</>
);
};
export default Usage;
The Card.tsx The CardProps
interface is defined with a generic type E
that extends React.ElementType
(which represents the type of HTML element or React component). The Component
variable is assigned the value of as
or the default value of 'div'
if as
is not provided.
The Card
component is declared as a function component using the React.FC
type, with the generic type CardProps<E>
. It takes in props of type CardProps<E>
, which includes the as
prop (to specify the underlying HTML element or React component) and the children
prop (to render the content inside the Card
component).
import React from 'react';
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
interface CardProps<E extends React.ElementType = 'div'> {
as?: E;
children: ReactNode;
}
const Card = <E extends React.ElementType = 'div'>(
{ as: Component = 'div', ...props }: ComponentPropsWithoutRef<typeof Component> & CardProps<E>
) => {
return <Component {...props}>{props.children}</Component>;
};
export default Card;
Text.tsx, The Text
component is declared as a function component that takes in props of type TextProps<C>
. It uses destructuring to extract the as
and children
props from the rest
object, and the remaining props are collected in the rest
object. The Component
variable is assigned the value of as
or the default value of 'span'
if as
is not provided.
import React from 'react';
type TextProps<C extends React.ElementType> = {
as?: C;
children: React.ReactNode;
} & React.ComponentPropsWithoutRef<C>;
const Text: React.FC<TextProps<React.ElementType>> = ({ as, children, ...rest }) => {
const Component = as || 'span';
return <Component {...rest}>{children}</Component>;
};
export default Text;
Header.tsx
import React from 'react';
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
type HeaderProps = React.ComponentPropsWithoutRef<'header'> & { children: ReactNode };
const Header: React.FC<HeaderProps> = ({ children, ...rest }) => {
return <header style={{ backgroundColor: 'lightblue', padding: '10px' }} {...rest}>
{children}
</header>;
};
export default Header;
const Header: React.FC<HeaderProps> = ({ children, ...rest }) => { ... }
creates a functional component namedHeader
usingReact.FC
:HeaderProps
specifies the type of props the component expects.children
andrest
destructuring pulls out specific props from theHeaderProps
object.
Footer.tsx
import React from 'react';
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
type FooterProps = React.ComponentPropsWithoutRef<'footer'> & { children: ReactNode };
const Footer: React.FC<FooterProps> = ({ children, ...rest }) => {
return <footer style={{ backgroundColor: 'lightgreen', padding: '10px' }} {...rest}>
{children}
</footer>;
};
export default Footer;
ConclusionComponentPropsWithoutRef
helps prevent prop-related errors during development. The ComponentPropsWithoutRef type in React TypeScript is a valuable tool for ensuring type safety when working with native HTML elements. It is particularly useful when creating higher-order components and when working with ref forwarding. By leveraging ComponentPropsWithoutRef, developers can write more reliable and maintainable code.