DEV Community

Cover image for React Fundamentals: Creating Custom Components
Bhanu Teja Pachipulusu
Bhanu Teja Pachipulusu

Posted on • Edited on • Originally published at blog.bhanuteja.dev

React Fundamentals: Creating Custom Components

Hello World πŸ‘‹

Welcome to the 5th article of the series My Review of Kent C. Dodds's EpicReact.Dev. Please note that this blog post series is just my review of the EpicReact.Dev workshop material. I am just trying to explain what I learned and understood in my own way. This is not in any way officially associated with Kent C. Dodds or EpicReact.Dev. You would learn a lot more when you actually go through the EpicReact.Dev video explanations and workshop material yourself. The workshop material is also self-paced and open source. So, if you want to do the workshop yourself, you can go to React Fundamentals Workshop Repo and follow the instructions there.

If you haven't read the previous articles in this series, please go and read them first before you continue. I will add links to the articles below.

  1. Introduction
  2. Javascript You Need To Know For React
  3. React Fundamentals - Intro to React Raw APIs
  4. React Fundamentals - Understanding JSX

In the previous article, you have learned different things about JSX like converting React.createElement() calls to JSX and vice-versa, interpolation in JSX, spreading props in JSX, etc. In this article, we will learn how to create custom components in JSX.

Table of Contents

The examples shown in this article are taken from Kent C. Dodds's React Fundamentals workshop repo.

Creating Basic Reusable Function

Consider the following JSX markup.

<div className="container">
    <div className="message">Hello World</div>
    <div className="message">GoodBye World</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Now, our goal is to avoid the duplication <div className="message">...</div>.

Like we normally would do in vanilla javascript, let's create a reusable function that takes the text as an argument and returns the JSX that we need.

function message(text) {
    return <div className="message">{text}</div>
}
Enter fullscreen mode Exit fullscreen mode

Now we can write our JSX markup in the following manner.

<div className="container">
    {message("Hello World")}
    {message("GoodBye World")}
</div>
Enter fullscreen mode Exit fullscreen mode

Let's refactor this a bit, instead of accepting the string value as an argument, let's pass an object that has a children key in it.

function message(props) {
    return <div className="message">{props.children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className="container">
    {message({children: "Hello World"})}
    {message({children: "GoodBye World"})}
</div>
Enter fullscreen mode Exit fullscreen mode

We can even go a step further and destructure the children prop.

function message({children}) {
    return <div className="message">{children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className="container">
    {message({children: "Hello World"})}
    {message({children: "GoodBye World"})}
</div>
Enter fullscreen mode Exit fullscreen mode

Using React.createElement

Previously we have seen that the first argument of the React.createElement() is the type of tag that we want to render.

For example, React.createElement('div', {}, 'Hello World') will render <div>Hello World</div>.

But, the first argument of the React.createElement() will also accept a function as its arguments that generate something renderable like JSX, an expression like string, number, etc.

So, let's refactor the above code and use React.createElement()

function message({children}) {
    return <div className="message">{children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className="container">
    {React.createElement(message, {children: "Hello World"})}
    {React.createElement(message, {children: "GoodBye World"})}
</div>
Enter fullscreen mode Exit fullscreen mode

Using JSX

In the previous article, we have seen how to convert React.createElement() calls to JSX.

For example, JSX for {React.createElement("div", {children: "Hello World"})} is <div>Hello World</div>

Let's try to use the similar approach to convert {React.createElement(message, {children: "Hello World"})} to JSX.

<message>Hello World</message>
Enter fullscreen mode Exit fullscreen mode

If we follow the same approach, we would end up with the above JSX markup.

As per our knowledge until now, the above code should work as intended. But it will not. It is because of how babel compiles JSX to React.createElement().

The above JSX will be compiled to React.createElement("message", {children: "Hello World"}) instead of React.createElement(message, {children: "Hello World"}). Notice the difference. In the first case, the argument is the string "message" whereas in the second case, the argument is the reference to the message function.

The way we can achieve this is very simple. We simply have to make the first letter of the name of the function as uppercase.

function Message({children}) {
    return <div className="message">{children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className="container">
    <Message>Hello World</Message>
    <Message>GoodBye World</Message>
</div>
Enter fullscreen mode Exit fullscreen mode

Now, this <Message>Hello World</Message> will be compiled to React.createElement(Message, {children: "Hello World"}) which is exactly what we need.

Check the below examples to see how Babel compiles each of the JSX formats.

JSX React.createElement()
<Capitalized /> React.createElement(Capitalized)
<property.access /> React.createElement(property.access)
<Property.Access /> React.createElement(Property.Access)
<Property['Access'] /> SyntaxError
<lowercase /> React.createElement('lowercase')
<kebab-case /> React.createElement('kebab-case')
<Upper-Kebab-Case /> React.createElement('Upper-Kebab-Case')
<Upper_Snake_Case /> React.createElement(Upper_Snake_Case)
<lower_snake_case /> React.createElement('lower_snake_case')

Validation with PropTypes

Consider the following Message component.

function Message({name}) {
    return <div className='message'>Hi, your name is {name}.</div>
}
Enter fullscreen mode Exit fullscreen mode

Let's use this component in the following way.

<Message name="foo" />
<Message />
<Message name={2} />
Enter fullscreen mode Exit fullscreen mode

This produces

// OK
Hi, your name is foo.

// Should ideally throw an error
Hi, your name is .

// Should ideally throw an error
Hi, your name is 2.
Enter fullscreen mode Exit fullscreen mode

So, If we pass a number as a name prop or if we don't pass any prop, even then the text is rendered, but ideally, it should throw an error because Hi, your name is . doesn't make sense.

Luckily, React gives us a way to achieve this using PropTypes.

const PropTypes = {
    string(props, propName, componentName) {
        if (typeof props[propName] !== 'string') {
            return new Error(`In component ${componentName}, ${propName} needs to be a string, but it was of type ${typeof props[propName]}`)
        }
    },
}

function Message({name}) {
    return <div className='message'>Hi, your name is {name}.</div>
}

// Component accepts an object as its `propTypes`. 
// Each key in that object is the name of each prop. 
// Each value is a function that takes (props, propName, componentName) 
//      as its arguments and returns an error if validation fails.
Message.propTypes = {
    name: PropTypes.string,
}
Enter fullscreen mode Exit fullscreen mode

Now, whenever you try to pass anything other than a string to name prop, it will throw an error.

Note:

  • PropTypes will be disabled by React in production environments for performance reasons.

Using prop-types Package

Since cases like the above are so common, React team created a package called prop-types which will work in a similar manner. For example, if we want the name prop to be required and also a string, we can do so with the prop-types package in the following manner.

function Message({name}) {
    return <div className='message'>Hi, your name is {name}.</div>
}

Message.propTypes = {
    name: PropTypes.isRequired.string,
}
Enter fullscreen mode Exit fullscreen mode

Check out prop-types repo for more details.

React Fragments

<div id='root'></div>
Enter fullscreen mode Exit fullscreen mode

Let's consider the following use case.
You have to add <span>Hello</span> and <span>World</span> to the rootElement using React.

In the end, the markup should look like

<div id='root'>
    <span>Hello</span>
    <span>World</span>
</div>
Enter fullscreen mode Exit fullscreen mode

Let's see if we can do this.

const rootElement = document.getElementById('root')

const elementOne = React.createElement('span', null, 'Hello')
const elementTwo = React.createElement('span', null, 'World')

ReactDOM.render(?????, rootElement)
Enter fullscreen mode Exit fullscreen mode

Now, what should be in the place of ????? in the last line. It can neither be elementOne nor elementTwo, because we want both of them to be rendered (not one). But ReactDOM.render() takes only one react element as an argument and then appends it to rootElement.

One way to achieve this is if we can wrap both of the elements in a new element.

const rootElement = document.getElementById('root')

const elementOne = React.createElement('span', null, 'Hello')
const elementTwo = React.createElement('span', null, 'World')

const combinedElement = React.createElement('div', null, elementOne, elementTwo)

ReactDOM.render(combinedElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

The above code may look fine, but it produces different HTML than what we needed.

<div id='root'>
    <div>
        <span>Hello</span>
        <span>World</span>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

This is the reason why you can't do something like the following in your code.

function Message() {
    return (
        <span>Hello</span>
        <span>World</span>
    )
}
Enter fullscreen mode Exit fullscreen mode

Because there is no way for babel to be able to convert this to a single React.createElement()

React Fragments are introduced in React v16.2.0 exactly to solve this problem. Now you can return multiple elements by just wrapping them around with React.Fragment.

For example,

function Message() {
    return (
        <React.Fragment>
            <span>Hello</span>
            <span>World</span>
        </React.Fragment>
    )
}
Enter fullscreen mode Exit fullscreen mode

React will ignore this React.Fragment when rendering.

So the previous problem can be solved now in the following way.

const elementOne = React.createElement('span', null, 'Hello')
const elementTwo = React.createElement('span', null, 'World')

const combinedElement = React.createElement(React.Fragment, null, elementOne, elementTwo)

ReactDOM.render(combinedElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

Note:

  • There is a shorthand representation for React.Fragment.
    • Instead of writing <React.Fragment>{childrent}</React.Fragment>, you can write something like <>{children}</>.
    • Both yield absolutely the same result.

What did you learn?

In this article, you learned about

  • Creating custom components.
  • The reason why the first letter of the custom component needs to be upper case.
  • Validating the props of the custom component using propTypes
  • Using prop-types package to validate props
  • Rendering multiple elements at the same level using React.Fragment

What's Next

In the next article, we will see how to style React elements. We will also see how to handle basic forms in React.

Until Next Time πŸ‘‹


If this was helpful to you, Please Like and Share so that it reaches others as well. To get email notifications on my latest articles, please subscribe to my blog by hitting the Subscribe button at the top of the page. You can also follow me on Twitter @pbteja1998.

Links and References:


You might also like the following articles that I wrote:

ko-fi

Top comments (0)