Taking advantage of Typescript & React.ComponentPropsWithoutRef in creating cleaner React components.

Taking advantage of Typescript & React.ComponentPropsWithoutRef in creating cleaner React components.

N.B: This article assumes a basic knowledge of using Typescript in React.

From providing intelliSense while writing code to making it easier to read your code, the improved developer experience that comes with using typescript in react cannot be downplayed.

This article aims at showing you how to mirror HTML attributes as props to react components and consequently, create cleaner components using Typescript.

The App.tsx file ( found at the root of an example react project ) that imports an Input component is shown below:

import Input from "../components/Input"

function App() {
  return (
    <div className="App">
      <Input name="firstName" placeholder="John Doe" type="text" />
    </div>
  );
}

export default App;

Below is the Input component, sort of just a wrapper around the HTML input element.

import React from "react";
interface IInput {
  name: string
  type: "text" 
  placeholder: string
}

const Input = ({ name, type, placeholder }: IInput) => {
  return <input name={name} type={type} placeholder={placeholder} />;
};

export default Input

This input component is assumed to be found in a components folder at the root of the react project in use. (It could be elsewhere)

This component requires that the props - name, type, and placeholder - are passed to it. These props, in turn, serve as values to the corresponding attributes on the HTML input element. There is a bit of redundancy in our Input component code - id, name, and type appeared thrice!!!

What if we can eliminate this redundancy ?

This is where React.ComponentPropsWithoutRef comes in handy.

The idea is to make the Props interface ( IInput in our input component ) extend React.ComponentPropsWithoutRef<< HTMLTagName >>. < HTMLTagName > refers to the tag name ( in lowercase ) of the HTML Element that's being wrapped.

In the previous Input component , < HTMLTagName > is ‘input’. On rewriting the Input component , we have :

import React from "react";
interface IInput extends React.ComponentPropsWithoutRef<'input'>{

}

const Input = ({ ...rest}: IInput) => {
  return <input {...rest}/>
};

export default Input

Now, the Input component is stripped of its redundancy. Typescript is still able to provide IntelliSense for the Input component without us having to explicitly type the props passed to it.

Doesn't …rest allow just any prop to get passed to the Input component ?

No, it doesn’t because of extends React.ComponentPropsWithoutRef<'input'>

Remove that and see your component letting every possible prop passed to it.I'm sure you don't want this 😃

What if I am a die-hard ‘type’ fan?

Unfortunately😒 , types can’t be extended like interfaces but something can still be done 😜.

The intersection symbol (&) can be used as follows:

import React from "react";
type IInput = React.ComponentPropsWithoutRef<'input'> & {

}

const Input = ({ ..rest}: IInput) => {
  return <input {...rest}/>
};

export default Input

What if I want to extend the props passed to my component ?

Yeah , that's no issue. Just like the norms , any custom props can be specified within the curly braces for the interface / type.

For instance , If our custom input element is to take gender as a prop, we’ll have :

// Interface guys
interface IInput extends React.ComponentPropsWithoutRef<'input'>{
  gender: string
}

// Type guys
type IInput= React.ComponentPropsWithoutRef<'input'> & {
    gender: string
}

Wrap Up

In this article, I was able to show the use of React.ComponentPropsWithoutRef and Typescript in creating cleaner react components. I hope you find this article helpful . Do well to share ;)

Looking forward to your comments below.

Feel free to connect with me on LinkedIn and Twitter.

See you in my next article 👋