Hi, I am Sanjeet Tiwari...
Let's talk about

Back to Blog

Wrapping HTML React Elements ft. TypeScript

You must be thinking - why is there a blog needed for a task as simple as wrapping HTML React Elements, right ?

Well, let’s start by looking at how many of the junior React developers today make wrappers of this kind in TypeScript.

This is something I saw in a project -

interface FieldProps {
  label: string;
  id: string;
  inputProps: any;
}

export default function Field({
  label,
  id,
  inputProps,
}: FieldProps) {
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} {...inputProps} />
    </>
  );
}

It’s a wrapper component Field which includes a label alongside the input which can be reused in multiple components like -

export function MyApp() {
  const [firstName, setFirstName] = useState("");
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        console.log("firstname - ", firstName);
      }}
    >
      <Field
        label="First Name"
        id="first-name"
        inputProps={{
          value: firstName,
          onChange: (e: any) => setFirstName(e.target.value),
        }}
      />
    </form>
  );
}

Issues

Now, let me be clear, the above code will work fine. But you are not taking advantage of TypeScript at all if you code this way.

I’ve got multiple issues with this kind of strategy -

  1. It’s lazy. Multiple uses of any just kills the purpose of TypeScript. Personally I believe, you should never use any any.
  2. High chances of unwanted errors and bugs, if inputProps are not carrying the valid props for Input Element, as their won’t be any validations due to the any keyword.
  3. You won’t get any suggestions when filling in inputProps in the above example.
  4. This can lead to unnecessary huge interfaces.

A Cleaner Approach

Interfaces are more powerful than you think, and as a matter of fact, React has provided us with interfaces which carries HTML attributes for all kinds of HTML elements.

These interfaces can be extended by the Props interface of the Wrapper component which will essentially provide it with all the attributes of the desired HTML element.

Now, let’s tweak the above component -

import { InputHTMLAttributes } from "react";

interface FieldProps extends InputHTMLAttributes<HTMLInputElement> {
  label: string;
}
export default function Field({ label, ...props }: FieldProps) {
  return (
    <>
      <label htmlFor={props.id}>
        {label}
      </label>
      <input {...props} />
    </>
  );
}

and we then use it like this -

export function MyApp() {
  const [firstName, setFirstName] = useState("");
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        console.log("firstname - ", firstName);
      }}
    >
      <Field
        label="First Name"
        id="first-name"
        value={firstName}
        onChange={(e) => setFirstName(e.target.value)}
      />
    </form>
  );
}

Much much cleaner, is it not ? Benefits of this approach -

How I got to know about this ?

I got to know about this approach from my good friend Ajit who was my colleague back when I was an employee of ECS Infosolutions (now GlobalLogic UK&I).

I am telling you this because valuable ideas and knowledge often emerge through collaboration and interaction with others, specially the people you work with!

Please let me know if there are more ways to further simplify this issue, or any other approaches.

Last updated on 25-11-2024