Introduction to React Components

May 22, 2024

react

I could say most of the performance issues I've dealt with in React are originated in the lack of understanding React components. Misusing components can cause unnecessary re-renders, repeated side-effects, lead to increase in memory usage, and slow your application performance. So let's start this series with an explanation of what React components are.

What is a React component?

It all originates with the desire to extend HTML with custom elements. While we already have access to a bunch of HTML elements such as a, input, form, and so on, wouldn't be nice we were able to create our own, more elaborated elements?

Say for example we wish to create a todo-item element that could be used like this:

html
<h1>My Todo List</h1>
<todo-item title="Buy milk" />

and the Web Browser were able to understand it and maybe render it as following:

https://my-app.com

My Todo List

What are the benefits of being able to do this? Well, after many years of jQuery, devs understood that being able to extend HTML with custom elements would make a positive impact on these areas:

  • Reusability: Reusing UI is just a matter of adding that element to your HTML
  • Modularity: Your app now becomes a stack of custom elements
  • Encapsulation: Your element logic is better encapsulated

A React component is not more than React's solution to the problem of extending HTML. When you create a React component, you are declaring a piece of UI that can be used as a custom HTML element:

js
const TodoItem = /* component definition here... */;
ReactDOM.createRoot(document.getElementById("some-container")).render(
  <TodoItem title="Buy milk" />
);

TodoItem is what React calls a component.

JSX

Only web browser vendors have the ability to extend HTML as they see fit. Framework authors cannot simply hope to persuade every vendor to add a mechanism that allows them to extend HTML (although, in fact, there is already an ongoing discussion about this through the W3C standard Web Components).

In order to circumvent this limitation, React team decided to create a language similar to HTML called JSX, which is why we can seemingly embed HTML-like code <TodoItem /> inside a JavaScript source file.

Since JSX is JavaScript, component names such as todo-item are not valid, and instead, we have to use camel case notation: TodoItem.

Component vs Element

The HTML standard refers to both, an element being used in HTML code: <input />, and the actual type of the element: HTMLInputElement, as elements. Because of that, I would have preferred React team had called components, elements too. But in React component and element are used to name different things.

A component is any type that semantically represents a custom HTML element and that can be used as an element type in JSX. This is because JSX syntax is actually sugar syntax for calling React.createElement() instead.

js
// <TodoItem title="Buy milk" /> is equivalent to:
const el = React.createElement(
  TodoItem, // element type
  {         // element props/attributes
    title: "Buy milk"
  },
)

console.log(el)
// { $$typeof: Symbol(react.element), type: TodoItem, ... }

Defining a React component

How do we actually define our component? If we attempt to create an element using null as its type, React will provide a hint about the types that can be used to define components.

>

React.createElement(null)

>

Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: null.

That means there are three ways of defining components:

  1. A string representing a built-in HTML element
  2. A class
  3. A function

String components

String components represent built-in HTML elements which don't actually need any definition as those components are already defined within the web browser. To create an element of this type, we simply use a string:

>React.createElement("input")
>

{$$typeof: Symbol(react.element), type: "input", ...}

Rendering elements

Notice the result of React.createElement(): all it does is return a representation of how our component should be instantiated at the rendering stage. The same happens in native HTML, writing <input type="checkbox" /> does not actually create an input, what it does is instruct the browser to create an input of type checkbox when rendering the page.

It is up to specific rendering libraries, such as React DOM, to process this representation and convert it into actual visual elements on the screen.

Validation

Bear in mind React.createElement() won't perform any validation to check if the actual string component represents a known HTML element. You could use string and React will happily accept it.

There is a benefit in this approach: React can be totally decoupled from the environment in which elements are going to be rendered, opening the door to use the same API to describe native elements in other platforms, for example: React Native

Class components

Let's try to define our custom <TodoItem title="..." /> component using a class.

>class TodoItem {}
>React.createElement(TodoItem)
>

Uncaught TypeError: Class constructor TodoItem cannot be invoked without 'new'

We get a type error. From the error message it seems React is trying to call our class without instantiating it first. After digging into React's source code I found React will only instantiate a class component if it finds the attribute isReactComponent in its prototype:

>class TodoItem {}
+TodoItem.prototype.isReactComponent = {}
>React.createElement(TodoItem)
>

Warning: TodoItem(...): No render method found on the returned component instance: you may have forgotten to define render. at TodoItem

Now React is able to use our class as a component, but warns us that our class does not define a render method.

The render() method in a component class is responsible for returning the actual elements that will constitute our component's UI.

If our custom <TodoItem title="Buy milk" /> renders as an input of type checkbox and a title, we need to return those elements as the result of the render() method.

Pay attention to how all the attributes we pass to our component will be available under the props attribute of our class component automatically.

>class TodoItem {
>  render() {
>    return (
>      <div>
>        <input type="checkbox" />
>        {this.props.title}
>      </div>
>    );
>}
>TodoItem.prototype.isReactComponent = {}
>React.createElement(TodoItem, { title: "Buy milk" })
>

{$$typeof: Symbol(react.element), type: class TodoItem, props: { title: "Buy milk" }, ...}

React.Component

React provides React.Component, which is intended to be inherited by our component class and will automatically mark it as a React component:

js
class TodoItem extends React.Component {
  render() {
    return (
      <div>
        <input type="checkbox" />
        {this.props.title}
      </div>
    )
  }
}

Function components

Function components are akin to extracting the render method from a class component. In this variation, we also receive the component's props as an argument to our function.

>function TodoItem(props) {
>  return (
>    <div>
>      <input type="checkbox" />
>      {props.title}
>    </div>
>  );
>}
>React.createElement(TodoItem, { title: "Buy milk" })
>

{$$typeof: Symbol(react.element), type: class TodoItem, props: { title: "Buy milk" }, ...}

Conclusion

In this introductory post, we've explored the world of React components and the different ways to define them. We've seen how class components use a traditional ES6 class syntax, while function components offer a more concise and lightweight approach. Both variants haven their own strengths and weaknesses, and each is suitable for specific use cases.

As we move forward in this series, we'll dive deeper into the inner workings of React components, exploring topics like rendering, reconciliation, and lifecycle methods.

In the next posts, we'll build upon this foundation, delving into the intricacies of how React components render themselves, how they reconcile changes with the DOM, and what happens during their lifecycle. So stay tuned for more in-depth explorations of the world of React!

anler.me

Thanks for reading!

© 2024-present. Anler Hdez. All rights reserved.