React Fundamentals: Creating Custom Components

Full-Stack Developer, Entrepreneur and Co-Founder of Coderplex.
Building https://coderplex.in
Checkout my portfolio at https://bhanuteja.dev
Search for a command to run...

Full-Stack Developer, Entrepreneur and Co-Founder of Coderplex.
Building https://coderplex.in
Checkout my portfolio at https://bhanuteja.dev
In this series, I will write different blog posts based on workshop content from [Kent C. Dodds](https://kentcdodds.com)'s [EpicReact.Dev](https://epicreact.dev) as I go through it.
Hello World 👋 This is the 6th 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 understo...
Authored in connection with the Write With Fauna program. Table of Contents Authentication Setting up Fauna in Next.js Installing Fauna Setting up Migrations Tool Authentication and Authorization in Fauna Next.js Serverless Function Setup for Fa...

I wanted to start blogging in Aug of 2020. I decided to use @hashnode for my blog. It was the best decision that I made. I wrote 27 technical articles so far. Most of them are about frontend web development. A thread 🧵 about each of these articles i...

Next.js has become my go-to framework for almost every project that I make. So, I made a starter template that I can just use and get started easily. In this article, I will show you how to use the starter template that I made and deploy it with Verc...

In this article, I will list out all the git commands that I use very frequently. This is not in any way a complete list, just the commands that I use very often. This is intended to be used as a quick reference to perform an action that you want. ...

In this article, we will see the order in which different useEffect callbacks and cleanups happen. We will also see how it differs when the app mounts, unmounts, updates. This image is taken from https://github.com/donavon/hook-flow. I took the ex...

Bhanu Teja Pachipulusu's blog
29 posts
Developer, Indie Maker and Blogger. Currently building MDX.one
Checkout my portfolio
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.
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.
The examples shown in this article are taken from Kent C. Dodds's React Fundamentals workshop repo.
Consider the following JSX markup.
<div className="container">
<div className="message">Hello World</div>
<div className="message">GoodBye World</div>
</div>
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>
}
Now we can write our JSX markup in the following manner.
<div className="container">
{message("Hello World")}
{message("GoodBye World")}
</div>
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>
}
<div className="container">
{message({children: "Hello World"})}
{message({children: "GoodBye World"})}
</div>
We can even go a step further and destructure the children prop.
function message({children}) {
return <div className="message">{children}</div>
}
<div className="container">
{message({children: "Hello World"})}
{message({children: "GoodBye World"})}
</div>
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>
}
<div className="container">
{React.createElement(message, {children: "Hello World"})}
{React.createElement(message, {children: "GoodBye World"})}
</div>
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>
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>
}
<div className="container">
<Message>Hello World</Message>
<Message>GoodBye World</Message>
</div>
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') |
Consider the following Message component.
function Message({name}) {
return <div className='message'>Hi, your name is {name}.</div>
}
Let's use this component in the following way.
<Message name="foo" />
<Message />
<Message name={2} />
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.
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,
}
Now, whenever you try to pass anything other than a string to name prop, it will throw an error.
Note:
prop-types PackageSince 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,
}
Check out prop-types repo for more details.
<div id='root'></div>
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>
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)
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)
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>
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>
)
}
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>
)
}
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)
Note:
React.Fragment. <React.Fragment>{childrent}</React.Fragment>, you can write something like <>{children}</>. In this article, you learned about
propTypesprop-types package to validate propsReact.FragmentIn the next article, we will see how to style React elements. We will also see how to handle basic forms in React.
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: