Writing A Custom Paragraph Component in React

Writing A Custom Paragraph Component in React

In this article, I'll be explaining how to create a custom paragraph component which will take a prop as well other valid HTML properties valid for a paragraph.

Let's say you were to write a custom paragraph component in React which would be called <ParaText />. You want to pass some string value as a property called text which would then be passed on to the p element like this:

<p>{text}</p>

How would you go about writing this component? You may think about listing all valid properties for a paragraph element and then passing it on, but it soon becomes clear that this approach is unrealistic:

const ParaText = ({ text, id, class, style, title}) => <p>{text}</p>;

Here we've added just 4 attributes (and the text prop) that would need to be passed on. If we were to include all necessary attributes we would need 10 props and that's not a good approach. We could instead use the spread syntax to collect the remaining props in an object that we will call rest for this tutorial.

const ParaText = ({ text, ...rest}) => <p {...rest}>{text}</p>;

So far it's all been JavaScript but now we'll use an interface to define the type of the properties object so that only valid properties are passed to the ParaText component. We'll call the interface ParaTextType.

interface ParaTextType {
  text: string;
  id?: string;
  class?: string;
  style?: string;
  title?: string;
}

const ParaText = ({  text, ...rest}: ParaTextType) => 
<p {...rest}>{text}</p>;

But our ParaTexType definition for what a paragraph should accept isn't as precise as the built-in ones in React. And we only want to include the text property in our custom component and intrinsically inherit the normal attributes of the native paragraph element. There are a number of ways to achieve this but for this tutorial, we'll take a look at three:

1. ComponentPropsWithoutRef

This interface is available on the React namespace and it provides the appropriate props for a given component type without any ref that the component accepts. So that means if you passed a paragraph to ComponentPropsWithoutRef like this:

React.ComponentPropsWithoutRef<"p">

You should get the valid props (attributes) for the paragraph element, which ParaTextType can extend. For brevity's sake, let's import ComponentPropsWithoutRef from React:


import { ComponentPropsWithoutRef } from 'react';

interface ParaTextType extends ComponentPropsWithoutRef<"p"> {
    text: string;
}

const ParaText = ({  text, ...rest}: ParaTextType) => <p {...rest}>{text}</p>;

Then you can simply use the component this way:

    <ParaText text="John" />

Another interface similar to ComponentPropsWithoutRef is ComponentProps. The latter provides a ref property which can be used in the paragraph element.

2. JSX.IntrinsicElements

The namespace JSX is available globally from which we can extract the type we want by indexingJSX.IntrinsicElements with the HTML node name:

    JSX.IntrinsicElements['p']

But we can't use extends with square brackets: this seems to be a TypeScript limitation. So we cannot do:

    interface ParaTexType extends JSX.IntrinsicElements['p']

We would have to hold it in a separate type variable. Let's call this variable ParaTexTypeFromJSX, which we would then use like this:

type ParaTexTypeFromJSX = JSX.IntrinsicElements['p'];

interface ParaTexType extends ParaTexTypeFromJSX {
  label: string;
}

const ParaText = ({  text, ...rest}: ParaTextType) => <p {...rest}>{text}</p>;

3. HTMLParagraphElement

You can also define the return value of ParaText1 which in the end is a functional component that renders a paragraph. So we get the properties (HTML attributes) for a paragraph and use the intersection literal (&) to include our text prop.

const ParaText1: React.FunctionComponent<
  React.HTMLAttributes<HTMLParagraphElement> & {
    text: string;
  }
> = ({ text, ...rest }: { text: string }) => <p {...rest}>{text}</p>;

Then you can then use the component as follows:

    <ParaText text="John" />

And you should get a paragraph element displaying "John". You could extend this component as you wish, like limiting the text length and using an ellipsis (...) afterwards.

Conclusion

We looked at how we can create or mirror a paragraph element in React using the built-in types. There are other interfaces, like InputHTMLAttributes, HTMLAttributes and HTMLProps that might as well get the job done or be a bad choice but for this tutorial, we'll keep it at 3.