Introduction to Components

11,218 words

Post contents

Before we can dive into how many front-end frameworks work, we need to set a baseline of information. If you're already familiar with how the DOM represents a tree and how the browser takes that information and uses it, great; You're ready to read ahead! Otherwise, it's strongly suggested that you look at our post introducing the concepts required to understand some baselines of this post.

You may have heard about various frameworks and libraries that modern front-end developers use to build large-scale applications. Among these frameworks are Angular, React, and Vue. While each of these libraries brings its own strengths and weaknesses, many of the core concepts are shared between them.

With this book, we will outline core concepts shared between them and how you can implement them in code in all three of the frameworks. This should provide a good reference when trying to learn one of these frameworks without pre-requisite knowledge or even trying to learn another framework with some pre-requisite of a different one.

First, let's explain why frameworks like Angular, React, or Vue differ from other libraries that may have come before them, like jQuery.

It all comes down to a single core concept at the heart of each: Componentization.

What's an App, Anyway?

Before we dive into the technical aspects, let's think about what an app consists of at a high level.

Take the following application into consideration.

A mockup of a file management application. Contains two sidebars on the left and right and a file list

Our app has many parts to it. For example, a sidebar containing navigation links, a list of files for a user to navigate, and a details pane about the user's selected file.

What's more, each part of the app needs different things.

The sidebar may not require complex programming logic, but we may want to style it with nice colors and highlight effects when the user hovers. Likewise, the file list may contain complex logic to handle a user right-clicking, dragging, and dropping files.

When you break it down, each part of the app has three primary concerns:

  • Logic — What does the app do?
  • Styling — How does the app look visually?
  • Structure — How is the app laid out?

While the mockup above does a decent job of displaying things visually, let's look at what the app looks like structurally:

The same mockup of the file list but with everything greyed out and showing just blocks

Here, each section is laid out without any additional styling: Simply a wireframe of what the page will look like, with each section containing blocks laid out in pretty straightforward ways. This is what HTML will help us build.

Now that we understand the structure, let's add some functionality. First, we'll include a small snippet of text in each section to outline our goals. We'd then use these as "acceptance" criteria in the future. This is what our logic will provide to the app.

The same wireframe mockup but with pointers to each section outlining what logic should be added to each section

Great! Now, let's go back and add the styling to recreate the mockup we had before!

The same mockup with styling as before

We can think of each step of the process like we're adding in a new programming language.

  • HTML is used for adding the structure of an application. The side nav might be a <nav> tag, for example.
  • JavaScript adds the logic of the application on top of the structure.
  • CSS makes everything look nice and potentially adds some minor UX improvements.

The way I typically think about these three pieces of tech is:

HTML is like building blueprints. It allows you to see the overarching pictures of what the result will look like. They define the walls, doors, and flow of a home.

JavaScript is like the electrical, plumbing, and appliances of the house. They allow you to interact with the building in a meaningful way.

CSS is like the paint and other decors that go into a home. They're what makes the house feel lived in and inviting. Of course, this decor does little without the rest of the home, but without the decor, it's a miserable experience.

Parts of the App

Now that we've introduced what an app looks like, let's go back for a moment. Remember how I said each app is made of parts? Let's explode the app's mockup into smaller pieces and look at them more in-depth.

A top-down 3d view of the stylized mockup

Here, we can more distinctly see how each part of the app has its own structure, styling, and logic.

The file list, for example, contains the structure of each file being its own item, logic about what buttons do which actions, and some CSS to make it look engaging.

While the code for this section might look something like this:

<section>	<button id="addButton"><span class="icon">plus</span></button>	<!-- ... --></section><ul>	<li>		<a href="/file/file_one">File one<span>12/03/21</span></a>	</li>	<!-- ... -->	<ul>		<script>			var addButton = document.querySelector("#addButton");			addButton.addEventListener("click", () => {				// ...			});		</script>	</ul></ul>

We might have a mental model to break down each section into smaller ones. If we use pseudocode to represent our mental model of the actual codebase, it might look something like this:

<files-buttons>	<add-button /></files-buttons><files-list>	<file name="File one" /></files-list>

Luckily, by using frameworks, this mental model can be reflected in real code!

Let's look at what <file> might look like in each framework:

const File = () => {	return (		<div>			<a href="/file/file_one">				File one<span>12/03/21</span>			</a>		</div>	);};

Here, we're defining a component that we call File, which contains a set of instructions for how React is to create the associated HTML when the component is used.

These HTML creation instructions are defined using a syntax very similar to HTML — but in JavaScript instead. This syntax is called "JSX" and powers the show for every React application.

While JSX looks closer to HTML than standard JS, it is not supported in the language itself. Instead, a compiler (or transpiler) like Babel must compile down to regular JS. Under the hood, this JSX compiles down to function calls.

For example, the above would be turned into:

var spanTag = React.createElement("span", null, "12/03/21");var aTag = React.createElement(	"a",	{		href: "/file/file_one",	},	"File one",	spanTag,);React.createElement("div", null, aTag);

While the above seems intimidating, it's worth mentioning that you'll likely never need to fall back on using createElement in an actual production application. Instead, this demonstrates why you need Babel in React applications.

You also likely do not need to set up Babel yourself from scratch. Most tools that integrate with React handle it out-of-the-box for you invisibly.

These are called "components." Components have various aspects to them, which we'll learn about throughout the course of this book.

We can see that each framework has its own syntax to display these components, but they often share more similarities than you might think.

Now that we've defined our components, there's a question: how do you use these components in HTML?

Rendering the App

While these components might look like simple HTML, they're capable of much more advanced usage. Because of this, each framework actually uses JavaScript under the hood to "draw" these components on-screen.

This process of "drawing" is called "rendering". This is not a one-and-done, however. A component may render at various times throughout its usage on-screen, particularly when it needs to update data shown on-screen; we'll learn more about this later in the chapter.

Traditionally, when you build out a website with just HTML, you'd define an index.html file like so:

<!-- index.html --><html>	<body>		<!-- Your HTML here -->	</body></html>

Similarly, all apps built with React, Angular, and Vue start with an index.html file.

<!-- index.html --><html>	<body>		<div id="root"></div>	</body></html>

Then, in JavaScript, you "render" a component into an element that acts as the "root" injection site for your framework to build your UI around.

import { createRoot } from "react-dom/client";const File = () => {	return (		<div>			<a href="/file/file_one">				File one<span>12/03/21</span>			</a>		</div>	);};createRoot(document.getElementById("root")).render(<File />);

React Rendering - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";const File = () => {	return (		<div>			<a href="/file/file_one">				File one<span>12/03/21</span>			</a>		</div>	);};createRoot(document.getElementById("root")).render(<File />);

Once a component is rendered, you can do a lot more with it!

For example, just like nodes in the DOM have relationships, so too can components.

Children, Siblings, and More, Oh My!

While our File component currently contains HTML elements, components may also contain other components!

const File = () => {	return (		<div>			<a href="/file/file_one">				File one<span>12/03/21</span>			</a>		</div>	);};const FileList = () => {	return (		<ul>			<li>				<File />			</li>		</ul>	);};

React Parent/Child - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";const File = () => {	return (		<div>			<a href="/file/file_one">				File one<span>12/03/21</span>			</a>		</div>	);};const FileList = () => {	return (		<ul>			<li>				<File />			</li>		</ul>	);};createRoot(document.getElementById("root")).render(<FileList />);

Looking through our File component, we'll notice that we're rendering multiple elements inside a single component. Funnily enough, this has the fun side effect that we can also render multiple components inside a parent component.

const FileList = () => {	return (		<ul>			<li>				<File />			</li>			<li>				<File />			</li>			<li>				<File />			</li>		</ul>	);};

React Component Reuse - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";const File = () => {	return (		<div>			<a href="/file/file_one">				File one<span>12/03/21</span>			</a>		</div>	);};const FileList = () => {	return (		<ul>			<li>				<File />			</li>			<li>				<File />			</li>			<li>				<File />			</li>		</ul>	);};createRoot(document.getElementById("root")).render(<FileList />);

This is a handy feature of components. It allows you to reuse aspects of your structure (and styling + logic, but I'm getting ahead of myself) without repeating yourself. It allows for a very DRY architecture where your code is declared once and reused elsewhere.

That stands for "Don't repeat yourself" and is often heralded as a gold standard of code quality!

It's worth remembering that we're using the term "parent" to refer to our FileList component in relation to our File component. This is because, like the DOM tree, each framework's set of components reflects a tree.

The FileList component is the parent of each File component. Likewise, each File component has a FileDate child

This means that the related File components are "siblings" of one another, each with a "parent" of FileList.

We can extend this hierarchical relationship to have "grandchildren" and beyond as well:

const FileDate = () => {	return <span>12/03/21</span>;};const File = () => {	return (		<div>			<a href="/file/file_one">				File one				<FileDate />			</a>		</div>	);};const FileList = () => {	return (		<ul>			<li>				<File />			</li>			<li>				<File />			</li>			<li>				<File />			</li>		</ul>	);};

React Component Hierarchy - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";const FileDate = () => {	return <span>12/03/21</span>;};const File = () => {	return (		<div>			<a href="/file/file_one">				File one				<FileDate />			</a>		</div>	);};const FileList = () => {	return (		<ul>			<li>				<File />			</li>			<li>				<File />			</li>			<li>				<File />			</li>		</ul>	);};createRoot(document.getElementById("root")).render(<FileList />);

Logic

HTML isn't the only thing components can store, however! As we mentioned earlier, apps (and, by extension, each part of the respective apps) require three parts:

  • Structure (HTML)
  • Styling (CSS)
  • Logic (JS)

Components can handle all three!

Let's look at how we can declare logic in a component by making file-date show the current date instead of a static date.

We'll start by adding a variable containing the current date in a human-readable string of MM/DD/YY.

const FileDate = () => {	const dateStr = `${		new Date().getMonth() + 1	}/${new Date().getDate()}/${new Date().getFullYear()}`;	return <span>12/03/21</span>;};

React Inline Logic - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";const FileDate = () => {	const dateStr = `${		new Date().getMonth() + 1	}/${new Date().getDate()}/${new Date().getFullYear()}`;	return <span>12/03/21</span>;};createRoot(document.getElementById("root")).render(<FileDate />);

We're not using this new dateStr variable yet. This is intentional; we'll use it here shortly.

While this logic to set this variable works, it's a bit verbose (and slow due to recreating the Date object thrice) - let's break it out into a method contained within the component.

function formatDate() {	const today = new Date();	// Month starts at 0, annoyingly	const monthNum = today.getMonth() + 1;	const dateNum = today.getDate();	const yearNum = today.getFullYear();	return monthNum + "/" + dateNum + "/" + yearNum;}
function formatDate() {	const today = new Date();	// Month starts at 0, annoyingly	const monthNum = today.getMonth() + 1;	const dateNum = today.getDate();	const yearNum = today.getFullYear();	return monthNum + "/" + dateNum + "/" + yearNum;}const FileDate = () => {	const dateStr = formatDate();	return <span>12/03/21</span>;};

React Extracted Logic - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";function formatDate() {	const today = new Date();	// Month starts at 0, annoyingly	const monthNum = today.getMonth() + 1;	const dateNum = today.getDate();	const yearNum = today.getFullYear();	return monthNum + "/" + dateNum + "/" + yearNum;}const FileDate = () => {	const dateStr = formatDate();	return <span>12/03/21</span>;};createRoot(document.getElementById("root")).render(<FileDate />);

Because React can easily access functions outside the component declaration, we decided to move it outside the component scope. This allows us to avoid re-declaring this function in every render, which the other frameworks don't do, thanks to different philosophies.

Intro to Side Effects

Let's verify that our formatDate method outputs the correct value by telling our components, "Once you're rendered on screen, console.log the value of that data."

import { useEffect } from "react";function formatDate() {	const today = new Date();	// Month starts at 0, annoyingly	const month = today.getMonth() + 1;	const date = today.getDate();	const year = today.getFullYear();	return month + "/" + date + "/" + year;}const FileDate = () => {	const dateStr = formatDate();	useEffect(() => {		console.log(dateStr);	}, []);	return <span>12/03/21</span>;};

React Side Effect Intro - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";import { useEffect } from "react";function formatDate() {	const today = new Date();	// Month starts at 0, annoyingly	const month = today.getMonth() + 1;	const date = today.getDate();	const year = today.getFullYear();	return month + "/" + date + "/" + year;}const FileDate = () => {	const dateStr = formatDate();	useEffect(() => {		console.log(dateStr);	}, []);	return <span>12/03/21</span>;};createRoot(document.getElementById("root")).render(<FileDate />);

Here, we're telling each respective framework to log the value of dateStr to the console once the component is rendered for the first time.

Wait, "for the first time?"

Yup! React, Angular, and Vue can all update (or "re-render") when needed.

For example, let's say you want to show dateStr to a user, but later in the day, the time switches over. While you'd have to handle the code to keep track of the time, the respective framework would notice that you've modified the values of dateStr and re-render the component to display the new value.

While the method each framework uses to tell when to re-render is different, they all have a highly stable method of doing so.

This feature is arguably the most significant advantage of building an application with one of these frameworks.

This ability to track data being changed relies on the concept of handling "side effects". While we'll touch on this more in our future chapter called "Side effects", you can think of a "side effect" as any change made to a component's data: Either through a user's input or the component's output changing.

Speaking of updating data on-screen - let's look at how we can dynamically display data on a page.

Display

While displaying the value in the console works well for debugging, it's not much help to the user. After all, more than likely, your users won't know what a console even is. Let's show dateStr on-screen

function formatDate() {	const today = new Date();	// Month starts at 0, annoyingly	const monthNum = today.getMonth() + 1;	const dateNum = today.getDate();	const yearNum = today.getFullYear();	return monthNum + "/" + dateNum + "/" + yearNum;}const FileDate = () => {	const dateStr = formatDate();	return <span>{dateStr}</span>;};

React Display - StackBlitz

Edit

Files

  • src
import { createRoot } from "react-dom/client";function formatDate() {	const today = new Date();	// Month starts at 0, annoyingly	const monthNum = today.getMonth() + 1;	const dateNum = today.getDate();	const yearNum = today.getFullYear();	return monthNum + "/" + dateNum + "/" + yearNum;}const FileDate = () => {	const dateStr = formatDate();	return <span>{dateStr}</span>;};createRoot(document.getElementById("root")).render(<FileDate />);