Edupala

Comprehensive Full Stack Development Tutorial: Learn Ionic, Angular, React, React Native, and Node.js with JavaScript

Understanding ComponentPropsWithoutRef in React: When and How to Use It

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.

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 named Header using React.FC:
    • HeaderProps specifies the type of props the component expects.
    • children and rest destructuring pulls out specific props from the HeaderProps 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;

Conclusion
ComponentPropsWithoutRef 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.

Understanding ComponentPropsWithoutRef in React: When and How to Use It

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top